def aperphot(fn, timekey=None, pos=[0,0], dap=[2,4,6], mask=None, verbose=False, nanval=999, resamp=None, retfull=False, ignorenan=True): """Do aperture photometry on a specified file. :INPUTS: pos : 2-sequence center of apertures (as if indexing: fn[pos[0], pos[1]]) dap : 3-sequence Photometry aperture DIAMETERS: -- target aperture (within which to sum flux) -- inner sky aperture -- outer sky aperture resamp : int Factor by which to interpolate frame before measuring photometry (in essence, does partial-pixel aperture photometry) Aperture masking: If no mask is passed in, use the star's input position and aperture diameters to create binary pixel masks for stellar and background photometry. If a mask is passed in, stellar and background aperture masks are generated from where all input mask elements equal 1 and 2, respectively. retfull: Also return arrays of target mask, sky mask, and frame used. This option is a memory hog! :OUTPUTS: :class:`phot` object. :EXAMPLE: :: import astropy.io.fits from astropy import wcs import numpy as np from phot import aperphot img='62_z_CDFs_goods_stamp_img.fits' #path to the image RA = 52.9898239 DEC = -27.7143114 hdulist = astropy.io.fits.open(img) w = wcs.WCS(hdulist['PRIMARY'].header) world = np.array([[RA, DEC]]) pix = w.wcs_world2pix(world,1) # Pixel coordinates of (RA, DEC) print "Pixel Coordinates: ", pix[0,0], pix[0,1] #call aperture function observation=aperphot(img, timekey=None, pos=[pix[0,0], pix[0,1]], dap=[4,8,12], resamp=2, retfull=False) # Print outputs print "Aperture flux:", observation.phot print "Background: ", observation.bg :REQUIREMENTS: scipy.interpolate, pyfits, numpy... """ # 2009-09-14 10:49 IJC: Created # 2010-01-15 14:20 IJC: Added numpy "_string" check # 2011-12-29 12:01 IJMC: Added peak pixel values to photometry report. # 2012-01-25 11:26 IJMC: Adding "resamp" option -- thanks to # K. Stevenson and J. Harrington of UCF for # the suggestion. # 2012-02-26 11:53 IJMC: Now return 'ntarg' and 'nsky' -- number of pixels used. # 2012-06-07 08:27 IJMC: 'peak' values are now corrected for the # resampling factor. # 2012-07-03 10:35 IJMC: Fixed a key bug: frames were not # correctly background-subtracted when # applying partial-pixel resampling. # 2012-10-19 13:41 IJMC: Documented 'retfull' option; changed default. # 2013-03-20 09:21 IJMC: More error-checking for saving header # keywords. Thanks to A. Weigel @ # ETH-Zurich for catching this! # 2014-05-07 16:05: added "ephot" by J. Xavier Prochaska, UCSC # 2014-08-28 09:32 IJMC: Added better checking for user-input # masks and resamp>1. Corrected x/y # indexing for non-square inputs arrays. # 2014-09-06 14:41 IJMC: Added better checking for dap & pos. # 2014-09-08 10:10 IJMC: Fix masking & nan-handling. from numpy import meshgrid, median,isfinite,sort,ndarray,string_ import numpy as np try: from astropy.io import fits as pyfits except: import pyfits from analysis import fixval from os import path from scipy import interpolate thisobs = phot() if pos is None: pos = 0, 0 if dap is None: dap = 1, 2, 3 x0, y0 = pos dap_targ, dap_skyinner, dap_skyouter = dap if resamp is None or resamp<1: resamp = 1 else: resamp = float(resamp) # Determine size: if isinstance(fn,str): nx = pyfits.getval(fn, 'NAXIS2') ny = pyfits.getval(fn, 'NAXIS1') elif isinstance(fn,ndarray): nx,ny = fn.shape nx0, ny0 = nx, ny nx = ((nx - 1)*resamp + 1.) # Avoid resampling at pixel locations ny = ((ny - 1)*resamp + 1.) # outside the original boundaries. # Generate or load masks: if mask is None: xx,yy = meshgrid(np.arange(ny)/resamp, np.arange(nx)/resamp) mask_targ = makemask(xx, yy, (y0, x0, dap_targ)) mask_s1 = makemask(xx, yy, (y0,x0, dap_skyinner)) mask_s2 = makemask(xx, yy, (y0,x0, dap_skyouter)) mask_sky = mask_s2 - mask_s1 userInputMask = False #pdb.set_trace() else: mask_targ = mask==1 mask_sky = mask==2 userInputMask = True # Load data frame: thisobs = phot() if pos is None: pos = 0, 0 if dap is None: dap = 1, 2, 3 if isinstance(fn,ndarray): frame = fn elif isinstance(fn, str) or isinstance(fn,string_): if not path.isfile(fn): print "file %s not found! exiting..." % fn return thisobs frame = pyfits.getdata(fn) fixval(frame, nanval) # Resample data frame badval = -12345.6789 if resamp>1: frame0 = frame.copy() mask_good0 = np.isfinite(frame0) frame0[True-mask_good0] = badval xx0 = range(nx0) yy0 = range(ny0) x1,y1 = np.arange(nx)/resamp, np.arange(ny)/resamp rectspline = interpolate.fitpack2.RectBivariateSpline(xx0, yy0, frame0, kx=1, ky=1, s=0) frame = rectspline(x1, y1) if userInputMask and \ ((frame.shape<>mask_targ.shape) or (frame.shape<>mask_sky.shape)): print "User-entered mask did not have the correct size for the resampling" print " input used (resamp=%1.3f). Beware!" % resamp stop if ignorenan: mask_good = np.isfinite(frame) mask_targ = mask_targ * mask_good mask_sky = mask_sky * mask_good #from pylab import * #pdb.set_trace() # Measure background and aperture photometry thisbg, thisebg = estbg(frame, mask=mask_sky, plotalot=verbose, rout=[3,99], verbose=verbose) if not np.isfinite(thisbg): thisbg = 0. thisphot = ((frame - thisbg)[mask_targ]).sum() /resamp/resamp peak = frame.max() peak_targ = frame[mask_targ].max() peak_annulus = frame[mask_sky].max() thisobs.bg=thisbg thisobs.ebg=thisebg thisobs.bgstr='phot.estbg: SDOM on bg histogram mean & dispersion after outlier rejection' thisobs.phot=thisphot thisobs.photstr='by-hand background-subtracted aperture photometry' thisobs.ntarg = mask_targ.sum()/resamp/resamp thisobs.nsky = mask_sky.sum()/resamp/resamp thisobs.peak = peak thisobs.peak_targ = peak_targ thisobs.peak_annulus = peak_annulus thisobs.peakstr = 'peak pixel value in frame' thisobs.peak_targstr = 'peak pixel value in target aperture' thisobs.peak_annulusstr = 'peak pixel value in sky annulus' thisobs.position = pos thisobs.positionstr = 'user-specified, zero-indexed pixel coordinates.' if isinstance(fn, str): header = pyfits.getheader(fn) if not timekey is None: if timekey in header: thisobs.time=header['timekey'] thisobs.timestr='heliocentric modified julian date' if 'object' in header: thisobs.object = header['object'] if 'exptime' in header: thisobs.exptime = header['exptime'] thisobs.aper = dap thisobs.aperstr = 'target, inner, outer aperture diameters, in pixels.' thisobs.filename=fn thisobs.resamp = resamp # Simple stats :: JXP 2014 May 6 var = thisphot + np.sqrt(thisobs.nsky)*thisobs.bg thisobs.ephot = np.sqrt(var) if retfull: thisobs.mask_targ = mask_targ thisobs.mask_sky = mask_sky thisobs.frame = frame if verbose>0: from pylab import figure, colorbar from nsdata import imshow figure(); imshow(frame*mask_targ); colorbar() figure(); imshow(frame*mask_sky); colorbar() return thisobs
def aperphot(fn, timekey=None, pos=[0,0], dap=[2,4,6], mask=None, verbose=False, nanval=999, resamp=None, retfull=False, ignorenan=True): """Do aperture photometry on a specified file. :INPUTS: pos : 2-sequence center of apertures (as if indexing: fn[pos[0], pos[1]]) dap : 3-sequence Photometry aperture DIAMETERS: -- target aperture (within which to sum flux) -- inner sky aperture -- outer sky aperture resamp : int Factor by which to interpolate frame before measuring photometry (in essence, does partial-pixel aperture photometry) Aperture masking: If no mask is passed in, use the star's input position and aperture diameters to create binary pixel masks for stellar and background photometry. If a mask is passed in, stellar and background aperture masks are generated from where all input mask elements equal 1 and 2, respectively. retfull: Also return arrays of target mask, sky mask, and frame used. This option is a memory hog! :OUTPUTS: :class:`phot` object. :EXAMPLE: :: import astropy.io.fits from astropy import wcs import numpy as np from phot import aperphot img='62_z_CDFs_goods_stamp_img.fits' #path to the image RA = 52.9898239 DEC = -27.7143114 hdulist = astropy.io.fits.open(img) w = wcs.WCS(hdulist['PRIMARY'].header) world = np.array([[RA, DEC]]) pix = w.wcs_world2pix(world,1) # Pixel coordinates of (RA, DEC) print "Pixel Coordinates: ", pix[0,0], pix[0,1] #call aperture function observation=aperphot(img, timekey=None, pos=[pix[0,0], pix[0,1]], dap=[4,8,12], resamp=2, retfull=False) # Print outputs print "Aperture flux:", observation.phot print "Background: ", observation.bg :REQUIREMENTS: scipy.interpolate, pyfits, numpy... """ # 2009-09-14 10:49 IJC: Created # 2010-01-15 14:20 IJC: Added numpy "_string" check # 2011-12-29 12:01 IJMC: Added peak pixel values to photometry report. # 2012-01-25 11:26 IJMC: Adding "resamp" option -- thanks to # K. Stevenson and J. Harrington of UCF for # the suggestion. # 2012-02-26 11:53 IJMC: Now return 'ntarg' and 'nsky' -- number of pixels used. # 2012-06-07 08:27 IJMC: 'peak' values are now corrected for the # resampling factor. # 2012-07-03 10:35 IJMC: Fixed a key bug: frames were not # correctly background-subtracted when # applying partial-pixel resampling. # 2012-10-19 13:41 IJMC: Documented 'retfull' option; changed default. # 2013-03-20 09:21 IJMC: More error-checking for saving header # keywords. Thanks to A. Weigel @ # ETH-Zurich for catching this! # 2014-05-07 16:05: added "ephot" by J. Xavier Prochaska, UCSC # 2014-08-28 09:32 IJMC: Added better checking for user-input # masks and resamp>1. Corrected x/y # indexing for non-square inputs arrays. # 2014-09-06 14:41 IJMC: Added better checking for dap & pos. # 2014-09-08 10:10 IJMC: Fix masking & nan-handling. from numpy import meshgrid, median,isfinite,sort,ndarray,string_ import numpy as np try: from astropy.io import fits as pyfits except: import pyfits from analysis import fixval from os import path from scipy import interpolate thisobs = phot() if pos is None: pos = 0, 0 if dap is None: dap = 1, 2, 3 x0, y0 = pos dap_targ, dap_skyinner, dap_skyouter = dap if resamp is None or resamp<1: resamp = 1 else: resamp = float(resamp) # Determine size: if isinstance(fn,str): nx = pyfits.getval(fn, 'NAXIS2') ny = pyfits.getval(fn, 'NAXIS1') elif isinstance(fn,ndarray): nx,ny = fn.shape nx0, ny0 = nx, ny nx = ((nx - 1)*resamp + 1.) # Avoid resampling at pixel locations ny = ((ny - 1)*resamp + 1.) # outside the original boundaries. # Generate or load masks: if mask==None: xx,yy = meshgrid(np.arange(ny)/resamp, np.arange(nx)/resamp) mask_targ = makemask(xx, yy, (y0, x0, dap_targ)) mask_s1 = makemask(xx, yy, (y0,x0, dap_skyinner)) mask_s2 = makemask(xx, yy, (y0,x0, dap_skyouter)) mask_sky = mask_s2 - mask_s1 userInputMask = False #pdb.set_trace() else: mask_targ = mask==1 mask_sky = mask==2 userInputMask = True # Load data frame: thisobs = phot() if pos is None: pos = 0, 0 if dap is None: dap = 1, 2, 3 if isinstance(fn,ndarray): frame = fn elif isinstance(fn, str) or isinstance(fn,string_): if not path.isfile(fn): print "file %s not found! exiting..." % fn return thisobs frame = pyfits.getdata(fn) fixval(frame, nanval) # Resample data frame badval = -12345.6789 if resamp>1: frame0 = frame.copy() mask_good0 = np.isfinite(frame0) frame0[True-mask_good0] = badval xx0 = range(nx0) yy0 = range(ny0) x1,y1 = np.arange(nx)/resamp, np.arange(ny)/resamp rectspline = interpolate.fitpack2.RectBivariateSpline(xx0, yy0, frame0, kx=1, ky=1, s=0) frame = rectspline(x1, y1) if userInputMask and \ ((frame.shape<>mask_targ.shape) or (frame.shape<>mask_sky.shape)): print "User-entered mask did not have the correct size for the resampling" print " input used (resamp=%1.3f). Beware!" % resamp stop if ignorenan: mask_good = np.isfinite(frame) mask_targ = mask_targ * mask_good mask_sky = mask_sky * mask_good #from pylab import * #pdb.set_trace() # Measure background and aperture photometry thisbg, thisebg = estbg(frame, mask=mask_sky, plotalot=verbose, rout=[3,99], verbose=verbose) if not np.isfinite(thisbg): thisbg = 0. thisphot = ((frame - thisbg)[mask_targ]).sum() /resamp/resamp peak = frame.max() peak_targ = frame[mask_targ].max() peak_annulus = frame[mask_sky].max() thisobs.bg=thisbg thisobs.ebg=thisebg thisobs.bgstr='phot.estbg: SDOM on bg histogram mean & dispersion after outlier rejection' thisobs.phot=thisphot thisobs.photstr='by-hand background-subtracted aperture photometry' thisobs.ntarg = mask_targ.sum()/resamp/resamp thisobs.nsky = mask_sky.sum()/resamp/resamp thisobs.peak = peak thisobs.peak_targ = peak_targ thisobs.peak_annulus = peak_annulus thisobs.peakstr = 'peak pixel value in frame' thisobs.peak_targstr = 'peak pixel value in target aperture' thisobs.peak_annulusstr = 'peak pixel value in sky annulus' thisobs.position = pos thisobs.positionstr = 'user-specified, zero-indexed pixel coordinates.' if isinstance(fn, str): header = pyfits.getheader(fn) if not timekey==None: if timekey in header: thisobs.time=header['timekey'] thisobs.timestr='heliocentric modified julian date' if 'object' in header: thisobs.object = header['object'] if 'exptime' in header: thisobs.exptime = header['exptime'] thisobs.aper = dap thisobs.aperstr = 'target, inner, outer aperture diameters, in pixels.' thisobs.filename=fn thisobs.resamp = resamp # Simple stats :: JXP 2014 May 6 var = thisphot + np.sqrt(thisobs.nsky)*thisobs.bg thisobs.ephot = np.sqrt(var) if retfull: thisobs.mask_targ = mask_targ thisobs.mask_sky = mask_sky thisobs.frame = frame if verbose>0: from pylab import figure, colorbar from nsdata import imshow figure(); imshow(frame*mask_targ); colorbar() figure(); imshow(frame*mask_sky); colorbar() return thisobs
def estbg(im, mask=None, bins=None, plotalot=False, rout=(3,200), badval=nan, verbose=False): """Estimate the background value of a masked image via histogram fitting. INPUTS: im -- numpy array. Input image. OPTIONAL INPUTS: mask -- numpy array. logical mask, False/0 in regions to ignore bins -- sequence. edges of bins to pass to HIST plotalot -- bool. Plot the histogram and fit. rout -- 2-tuple of (nsigma, niter) for analysis.removeoutliers. Set to (Inf, 0) to not cut any outliers. badval -- value returned when things go wrong. OUTPUT: b, s_b -- tuple of (background, error on background) from gaussian fit. Note that the error is analagous to the standard deviation on the mean COMMENTS: The fit parameters appear to be robust across a fairly wide range of bin sizes. """ # 2009-09-02 17:13 IJC: Created! # 2009-09-04 15:07 IJC: Added RemoveOutliers option. Use only non-empty bins in fit. # 2009-09-08 15:32 IJC: Error returned is now divided by sqrt(N) for SDOM # 2009-11-03 00:16 IJC: Improved guess for gaussian dispersion # 2011-05-18 11:47 IJMC: Moved (e)gaussian imports to analysis. # 2012-01-01 21:04 IJMC: Added badval option # 2012-08-15 17:45 IJMC: Numpy's new histogram no longer accepts 'new' keyword # 2013-03-20 08:22 IJMC: Now works better even for small numbers # of pixels; thanks to A. Weigel @ # ETH-Zurich for catching this! # 2014-08-29 10:05 IJMC: Added verbosity flag. # 2014-09-25 14:29 IJMC: Added check for no usability from numpy import histogram, mean, median, sqrt, linspace, isfinite, ones,std from pylab import find from scipy import optimize from analysis import removeoutliers, egaussian, gaussian, stdr if plotalot: from pylab import figure, errorbar, plot, colorbar, title, hist, mean, std #from analysis import imshow def gaussianChiSquared(guess, x, y, err): return (egaussian(guess, x, y, e=err)**2).sum() if mask is None: mask = ones(im.shape) dat = im.ravel()[find(mask<>0)] if plotalot: figure(); plot(im.ravel()); plot(dat) print mean(dat), std(dat), rout[0]*std(dat) print len(dat), (abs(dat-mean(dat))<(rout[0]*std(dat))).sum() figure(); plot(dat-mean(dat)); plot([0,len(dat)], [rout[0]*std(dat),rout[0]*std(dat)],'--k') plot([0,len(dat)], [-rout[0]*std(dat),-rout[0]*std(dat)],'--k') dat = removeoutliers(dat, rout[0], remove='both', center='mean', niter=rout[1], verbose=plotalot) ndat = len(dat) if ndat==0: if verbose>0: print "No data to work with!" return (badval, badval) if bins is None: if plotalot or verbose: print "no bins entered!" datmean = dat.mean() datstd = stdr(dat, nsigma=3) nunique = len(np.unique(dat.ravel())) #pdb.set_trace() if nunique > len(dat)/20.: dobin = False elif nunique>1: dobin = True bins = linspace(dat.min(), dat.max(), nunique/2) else: dobin = True bins = np.array([dat[0]-1, dat[0]+1]) if plotalot or verbose: print "dat.mean, dat.std>>" + str((dat.mean(), dat.std())) #if plotalot: # figure(); hout = hist(dat[datIndex],bins) #else: if dobin: binwidth = mean(bins[1::]-bins[:-1]) bincenter = 0.5*(bins[1::]+bins[:-1]) datIndex = (dat>=bins.min()) * (dat<=bins.max()) hout = histogram(dat[datIndex], bins) #,new=True) gy = hout[0] erry = sqrt(gy) usableIndex = gy>0 if usableIndex.size==0: return badval, badval eff_binwidth = mean(bins[usableIndex][1::]-bins[usableIndex][:-1]) guess = [gy.sum()*eff_binwidth, std(dat[datIndex]), median(dat[datIndex])] if 1.0*usableIndex.sum()/usableIndex.size < 0.5: out = guess else: out = optimize.fmin(gaussianChiSquared, guess, \ args=(bincenter[usableIndex],gy[usableIndex], erry[usableIndex]), \ disp=plotalot) if plotalot: from pylab import figure, errorbar, plot, colorbar, title from nsdata import imshow print 'guess>>',guess print 'fit>>',out figure() imshow(im); colorbar() figure() errorbar(bincenter[usableIndex], gy[usableIndex], erry[usableIndex], fmt='ob') plot(bincenter, gaussian(out, bincenter),'-r', linewidth=2) title('Mean: %f, Std. Dev.: %f' % (out[2], out[1])) ret = out[2], out[1]/sqrt(ndat) else: ret = datmean, datstd/sqrt(ndat) return ret
def estbg(im, mask=None, bins=None, plotalot=False, rout=(3,200), badval=nan, verbose=False): """Estimate the background value of a masked image via histogram fitting. INPUTS: im -- numpy array. Input image. OPTIONAL INPUTS: mask -- numpy array. logical mask, False/0 in regions to ignore bins -- sequence. edges of bins to pass to HIST plotalot -- bool. Plot the histogram and fit. rout -- 2-tuple of (nsigma, niter) for analysis.removeoutliers. Set to (Inf, 0) to not cut any outliers. badval -- value returned when things go wrong. OUTPUT: b, s_b -- tuple of (background, error on background) from gaussian fit. Note that the error is analagous to the standard deviation on the mean COMMENTS: The fit parameters appear to be robust across a fairly wide range of bin sizes. """ # 2009-09-02 17:13 IJC: Created! # 2009-09-04 15:07 IJC: Added RemoveOutliers option. Use only non-empty bins in fit. # 2009-09-08 15:32 IJC: Error returned is now divided by sqrt(N) for SDOM # 2009-11-03 00:16 IJC: Improved guess for gaussian dispersion # 2011-05-18 11:47 IJMC: Moved (e)gaussian imports to analysis. # 2012-01-01 21:04 IJMC: Added badval option # 2012-08-15 17:45 IJMC: Numpy's new histogram no longer accepts 'new' keyword # 2013-03-20 08:22 IJMC: Now works better even for small numbers # of pixels; thanks to A. Weigel @ # ETH-Zurich for catching this! # 2014-08-29 10:05 IJMC: Added verbosity flag. # 2014-09-25 14:29 IJMC: Added check for no usability from numpy import histogram, mean, median, sqrt, linspace, isfinite, ones,std from pylab import find from scipy import optimize from analysis import removeoutliers, egaussian, gaussian, stdr if plotalot: from pylab import figure, errorbar, plot, colorbar, title, hist, mean, std #from analysis import imshow def gaussianChiSquared(guess, x, y, err): return (egaussian(guess, x, y, e=err)**2).sum() if mask==None: mask = ones(im.shape) dat = im.ravel()[find(mask<>0)] if plotalot: figure(); plot(im.ravel()); plot(dat) print mean(dat), std(dat), rout[0]*std(dat) print len(dat), (abs(dat-mean(dat))<(rout[0]*std(dat))).sum() figure(); plot(dat-mean(dat)); plot([0,len(dat)], [rout[0]*std(dat),rout[0]*std(dat)],'--k') plot([0,len(dat)], [-rout[0]*std(dat),-rout[0]*std(dat)],'--k') dat = removeoutliers(dat, rout[0], remove='both', center='mean', niter=rout[1], verbose=plotalot) ndat = len(dat) if ndat==0: if verbose>0: print "No data to work with!" return (badval, badval) if bins==None: if plotalot or verbose: print "no bins entered!" datmean = dat.mean() datstd = stdr(dat, nsigma=3) nunique = len(np.unique(dat.ravel())) #pdb.set_trace() if nunique > len(dat)/20.: dobin = False elif nunique>1: dobin = True bins = linspace(dat.min(), dat.max(), nunique/2) else: dobin = True bins = np.array([dat[0]-1, dat[0]+1]) if plotalot or verbose: print "dat.mean, dat.std>>" + str((dat.mean(), dat.std())) #if plotalot: # figure(); hout = hist(dat[datIndex],bins) #else: if dobin: binwidth = mean(bins[1::]-bins[:-1]) bincenter = 0.5*(bins[1::]+bins[:-1]) datIndex = (dat>=bins.min()) * (dat<=bins.max()) hout = histogram(dat[datIndex], bins) #,new=True) gy = hout[0] erry = sqrt(gy) usableIndex = gy>0 if usableIndex.size==0: return badval, badval eff_binwidth = mean(bins[usableIndex][1::]-bins[usableIndex][:-1]) guess = [gy.sum()*eff_binwidth, std(dat[datIndex]), median(dat[datIndex])] if 1.0*usableIndex.sum()/usableIndex.size < 0.5: out = guess else: out = optimize.fmin(gaussianChiSquared, guess, \ args=(bincenter[usableIndex],gy[usableIndex], erry[usableIndex]), \ disp=plotalot) if plotalot: from pylab import figure, errorbar, plot, colorbar, title from nsdata import imshow print 'guess>>',guess print 'fit>>',out figure() imshow(im); colorbar() figure() errorbar(bincenter[usableIndex], gy[usableIndex], erry[usableIndex], fmt='ob') plot(bincenter, gaussian(out, bincenter),'-r', linewidth=2) title('Mean: %f, Std. Dev.: %f' % (out[2], out[1])) ret = out[2], out[1]/sqrt(ndat) else: ret = datmean, datstd/sqrt(ndat) return ret