def get_flux_and_err(imagedat, psfmodel, xy, ntestpositions=100, psfradpix=3, apradpix=3, skyannpix=None, skyalgorithm='sigmaclipping', setskyval=None, recenter_target=True, recenter_fakes=True, exptime=1, exact=True, ronoise=1, phpadu=1, verbose=False, debug=False): """ Measure the flux and flux uncertainty for a source at the given x,y position using both aperture and psf-fitting photometry. Flux errors are measured by planting fake psfs or empty apertures into the sky annulus and recovering a distribution of fluxes with forced photometry. :param imagedat: target image numpy data array (with the star still there) :param psfmodel: psf model fits file or a 4-tuple with [gaussparam,lookuptable,psfmag,psfzpt] :param xy: x,y position of the center of the fake planting field :param ntestpositions: number of test positions for empty apertures and/or fake stars to use for determining the flux error empirically. :param psfradpix: radius to use for psf fitting, in pixels :param apradpix: radius of photometry aperture, in pixels :param skyannpix: inner and outer radius of sky annulus, in pixels :param skyalgorithm: algorithm to use for determining the sky value from the pixels within the sky annulus: 'sigmaclipping' or 'mmm' :param setskyval: if not None, use this value for the sky, ignoring the skyannulus :param recenter_target: use cntrd to locate the target center near the given xy position. :param recenter_fakes: recenter on each planted fake when recovering it :param exptime: exposure time of the image, for determining poisson noise :param ronoise: read-out noise, for determining aperture flux error analytically :param phpadu: photons-per-ADU, for determining aper flux err analytically :param verbose: turn verbosity on :param debug: enter pdb debugging mode :return: apflux, apfluxerr, psfflux, psffluxerr The flux measured through the given aperture and through psf fitting, along with associated errors. """ if not np.any(skyannpix): skyannpix = [8, 15] # locate the target star center position x, y = xy if recenter_target: x, y = cntrd(imagedat, x, y, psfradpix) if x < 0 or y < 0: print("WARNING [photfunctions.py] : recentering failed") import pdb pdb.set_trace() # do aperture photometry directly on the source # (Note : using an arbitrary zeropoint of 25 here) aperout = aper(imagedat, x, y, phpadu=phpadu, apr=apradpix, skyrad=skyannpix, setskyval=setskyval, zeropoint=25, exact=exact, verbose=verbose, skyalgorithm=skyalgorithm, debug=debug) apmag, apmagerr, apflux, apfluxerr, sky, skyerr, apbadflag, apoutstr = \ aperout # define a set of test position points that uniformly samples the sky # annulus region, for planting empty apertures and/or fake stars rmin = float(skyannpix[0]) rmax = float(skyannpix[1]) u = np.random.uniform(rmin, rmax, ntestpositions) v = np.random.uniform(0, rmin + rmax, ntestpositions) r = np.where(v < u, u, rmax + rmin - u) theta = np.random.uniform(0, 2 * np.pi, ntestpositions) xtestpositions = r * np.cos(theta) + x ytestpositions = r * np.sin(theta) + y psfflux = psffluxerr = np.nan if psfmodel is not None: # set up the psf model realization gaussparam, lookuptable, psfmag, psfzpt = rdpsfmodel(psfmodel) psfmodel = [gaussparam, lookuptable, psfmag, psfzpt] pk = pkfit_class(imagedat, gaussparam, lookuptable, ronoise, phpadu) # do the psf fitting try: scale = pk.pkfit_fast_norecenter(1, x, y, sky, psfradpix) psfflux = scale * 10 ** (0.4 * (25. - psfmag)) except RuntimeWarning: print("PythonPhot.pkfit_norecenter failed.") psfflux = np.nan if np.isfinite(psfflux): # remove the target star from the image imagedat = addtoimarray(imagedat, psfmodel, [x, y], fluxscale=-psfflux) # plant fakes and recover their fluxes with psf fitting # imdatsubarray = imagedat[y-rmax-2*psfradpix:y+rmax+2*psfradpix, # x-rmax-2*psfradpix:x+rmax+2*psfradpix] fakecoordlist, fakefluxlist = [], [] for xt, yt in zip(xtestpositions, ytestpositions): # To ensure appropriate sampling of sub-pixel positions, # we assign random sub-pixel offsets to each position. xt = int(xt) + np.random.random() yt = int(yt) + np.random.random() fakefluxaper, fakefluxpsf, fakecoord = add_and_recover( imagedat, psfmodel, [xt, yt], fluxscale=psfflux, cleanup=True, psfradius=psfradpix, recenter=recenter_fakes) if np.isfinite(fakefluxpsf): fakecoordlist.append(fakecoord) fakefluxlist.append(fakefluxpsf) fakefluxlist = np.array(fakefluxlist) fakefluxmean, fakefluxsigma = gaussian_fit_to_histogram(fakefluxlist) if abs(fakefluxmean - psfflux) > fakefluxsigma and verbose: print("WARNING: psf flux may be biased. Fake psf flux tests " "found a significantly non-zero sky value not accounted for " "in measurement of the target flux: \\" "Mean psf flux offset in sky annulus = %.3e\\" % (fakefluxmean - psfflux) + "sigma of fake flux distribution = %.3e" % fakefluxsigma + "NOTE: this is included as a systematic error, added in " "quadrature to the psf flux err derived from fake psf " "recovery.") psfflux_poissonerr = (poissonErr(psfflux * exptime, confidence=1) / exptime) # Total flux error is the quadratic sum of the poisson noise with # the systematic (shift) and statistical (dispersion) errors # inferred from fake psf planting and recovery psffluxerr = np.sqrt(psfflux_poissonerr**2 + (fakefluxmean - psfflux)**2 + fakefluxsigma**2) # drop down empty apertures and recover their fluxes with aperture phot # NOTE : if the star was removed for psf fitting, then we take advantage # of that to get aperture flux errors with the star gone. emptyaperout = aper(imagedat, np.array(xtestpositions), np.array(ytestpositions), phpadu=phpadu, apr=apradpix, setskyval=sky, zeropoint=25, exact=False, verbose=verbose, skyalgorithm=skyalgorithm, debug=debug) emptyapflux = emptyaperout[2] if np.any(np.isfinite(emptyapflux)): emptyapmeanflux, emptyapsigma = gaussian_fit_to_histogram(emptyapflux) emptyapbias = abs(emptyapmeanflux) - emptyapsigma if np.any(emptyapbias > 0) and verbose: print("WARNING: aperture flux may be biased. Empty aperture flux tests" " found a significantly non-zero sky value not accounted for in " "measurement of the target flux: \\" "Mean empty aperture flux in sky annulus = %s\\" % emptyapmeanflux + "sigma of empty aperture flux distribution = %s" % emptyapsigma) if np.iterable(apflux): apflux_poissonerr = np.array( [poissonErr(fap * exptime, confidence=1) / exptime for fap in apflux]) else: apflux_poissonerr = (poissonErr(apflux * exptime, confidence=1) / exptime) apfluxerr = np.sqrt(apflux_poissonerr**2 + emptyapbias**2 + emptyapsigma**2) else: if np.iterable(apradpix): apfluxerr = [np.nan for aprad in apradpix] else: apfluxerr = np.nan if psfmodel is not None and np.isfinite(psfflux): # return the target star back into the image imagedat = addtoimarray(imagedat, psfmodel, [x, y], fluxscale=psfflux) if debug > 1: import pdb pdb.set_trace() return apflux, apfluxerr, psfflux, psffluxerr, sky, skyerr
def add_and_recover(imagedat, psfmodel, xy, fluxscale=1, psfradius=5, skyannpix=None, skyalgorithm='sigmaclipping', setskyval=None, recenter=False, ronoise=1, phpadu=1, cleanup=True, verbose=False, debug=False): """ Add a single fake star psf model to the image at the given position and flux scaling, re-measure the flux at that position and report it, Also deletes the planted psf from the imagedat array so that we don't pollute that image array. :param imagedat: target image numpy data array :param psfmodel: psf model fits file or tuple with [gaussparam,lookuptable] :param xy: x,y position for fake psf, using the IDL/python convention where [0,0] is the lower left corner. :param fluxscale: flux scaling to apply to the planted psf :param recenter: use cntrd to locate the center of the added psf, instead of relying on the input x,y position to define the psf fitting :param cleanup: remove the planted psf from the input imagedat array. :return: """ if not skyannpix: skyannpix = [8, 15] # add the psf to the image data array imdatwithpsf = addtoimarray(imagedat, psfmodel, xy, fluxscale=fluxscale) # TODO: allow for uncertainty in the x,y positions gaussparam, lookuptable, psfmag, psfzpt = rdpsfmodel(psfmodel) # generate an instance of the pkfit class for this psf model # and target image pk = pkfit_class(imdatwithpsf, gaussparam, lookuptable, ronoise, phpadu) x, y = xy if debug: from .photfunctions import showpkfit from matplotlib import pyplot as pl, cm fig = pl.figure(3) showpkfit(imdatwithpsf, psfmodel, xy, 11, fluxscale, verbose=True) fig = pl.figure(1) pl.imshow(imdatwithpsf[y - 20:y + 20, x - 20:x + 20], cmap=cm.Greys, interpolation='nearest') pl.colorbar() import pdb pdb.set_trace() if recenter: xc, yc = cntrd(imdatwithpsf, x, y, psfradius, verbose=verbose) if xc > 0 and yc > 0 and abs(xc - xy[0]) < 5 and abs(yc - xy[1]) < 5: x, y = xc, yc # do aperture photometry to get the sky aperout = aper(imdatwithpsf, x, y, phpadu=phpadu, apr=psfradius * 3, skyrad=skyannpix, setskyval=(setskyval is not None and setskyval), zeropoint=psfzpt, exact=False, verbose=verbose, skyalgorithm=skyalgorithm, debug=debug) apmag, apmagerr, apflux, apfluxerr, sky, skyerr, apbadflag, apoutstr\ = aperout # do the psf fitting try: scale = pk.pkfit_fast_norecenter(1, x, y, sky, psfradius) fluxpsf = scale * 10 ** (-0.4 * (psfmag - psfzpt)) except RuntimeWarning: print("photfunctions.add_and_recover failed on RuntimeWarning") fluxpsf = -99 if cleanup: # remove the fake psf from the image imagedat = addtoimarray(imdatwithpsf, psfmodel, xy, fluxscale=-fluxscale) return apflux[0], fluxpsf, [x, y]