def decam_psf(filt, fwhm): if filt not in 'ugrizY': tpsf = psfmod.moffat_psf(fwhm, stampsz=511, deriv=False) return psfmod.SimplePSF(tpsf) fname = os.path.join(os.environ['DECAM_DIR'], 'data', 'psfs', 'psf_%s_deconv_mod.fits.gz' % filt[0]) normalizesz = 59 tpsf = fits.getdata(fname).T.copy() tpsf /= numpy.sum(psfmod.central_stamp(tpsf, normalizesz)) # omitting central_stamp here places too much # emphasis on the wings relative to the pipeline estimate. tpsffwhm = psfmod.neff_fwhm(psfmod.central_stamp(tpsf)) from scipy.ndimage.filters import convolve if tpsffwhm < fwhm: convpsffwhm = numpy.sqrt(fwhm**2. - tpsffwhm**2.) convpsf = psfmod.moffat_psf(convpsffwhm, stampsz=39, deriv=False) tpsf = convolve(tpsf, convpsf, mode='constant', cval=0., origin=0) else: convpsffwhm = 0. tpsf = psfmod.stamp2model(numpy.array([tpsf, tpsf, tpsf, tpsf]), normalize=normalizesz) nlinperpar = 3 pixsz = 9 extraparam = numpy.zeros(1, dtype=[('convparam', 'f4', 3 * nlinperpar + 1), ('resparam', 'f4', (nlinperpar, pixsz, pixsz))]) extraparam['convparam'][0, 0:4] = [convpsffwhm, 1., 0., 1.] extraparam['resparam'][0, :, :, :] = 0. tpsf.extraparam = extraparam tpsf.fitfun = partial(psfmod.fit_linear_static_wing, filter=filt) return tpsf
def compute_stats(xs, ys, impsfstack, psfstack, weightstack, imstack, flux): residstack = impsfstack - psfstack norm = numpy.sum(psfstack, axis=(1, 2)) psfstack = psfstack / (norm + (norm == 0)).reshape(-1, 1, 1) qf = numpy.sum(psfstack*(weightstack > 0), axis=(1, 2)) fluxunc = numpy.sum(psfstack**2.*weightstack**2., axis=(1, 2)) fluxunc = fluxunc + (fluxunc == 0)*1e-20 fluxunc = (fluxunc**(-0.5)).astype('f4') posunc = [numpy.zeros(len(qf), dtype='f4'), numpy.zeros(len(qf), dtype='f4')] psfderiv = numpy.gradient(-psfstack, axis=(1, 2)) for i, p in enumerate(psfderiv): dp = numpy.sum((p*weightstack*flux[:, None, None])**2., axis=(1, 2)) dp = dp + (dp == 0)*1e-40 dp = dp**(-0.5) posunc[i][:] = dp rchi2 = numpy.sum(residstack**2.*weightstack**2.*psfstack, axis=(1, 2)) / (qf + (qf == 0.)*1e-20).astype('f4') fracfluxn = numpy.sum(impsfstack*(weightstack > 0)*psfstack, axis=(1, 2)) fracfluxd = numpy.sum(imstack*(weightstack > 0)*psfstack, axis=(1, 2)) fracfluxd = fracfluxd + (fracfluxd == 0)*1e-20 fracflux = (fracfluxn / fracfluxd).astype('f4') fluxlbs, dfluxlbs = compute_lbs_flux(impsfstack, psfstack, weightstack, flux/norm) fluxlbs = fluxlbs.astype('f4') dfluxlbs = dfluxlbs.astype('f4') fwhm = psfmod.neff_fwhm(psfstack).astype('f4') return OrderedDict([('dx', posunc[0]), ('dy', posunc[1]), ('dflux', fluxunc), ('qf', qf), ('rchi2', rchi2), ('fracflux', fracflux), ('fluxlbs', fluxlbs), ('dfluxlbs', dfluxlbs), ('fwhm', fwhm)])
def spread_model(impsfstack, psfstack, weightstack): # need to convolve psfs with 1/16 FWHM exponential # can get FWHM from n_eff # better way? n_eff can be a bit annoying; not necessarily what one # expects if there's a sharp peak on a broad background. # spread_model is on the whole a bit goofy: one sixteenth of a FWHM is very # little. So this is really more like the significance of the derivative # of the PSF with radius, which I would compute a bit differently. # still, other people compute spread_model, and it's well defined, so... import galconv fwhm = psfmod.neff_fwhm(psfstack) sigma = fwhm / 16. re = sigma * 1.67834699 expgalstack = galconv.gal_psfstack_conv(re, 0, 0, galconv.ExpGalaxy, numpy.eye(2), 0, 0, psfstack) GWp = numpy.sum(expgalstack * weightstack**2 * impsfstack, axis=(1, 2)) PWp = numpy.sum(psfstack * weightstack**2 * impsfstack, axis=(1, 2)) GWP = numpy.sum(expgalstack * weightstack**2 * psfstack, axis=(1, 2)) PWP = numpy.sum(psfstack**2 * weightstack**2, axis=(1, 2)) GWG = numpy.sum(expgalstack**2 * weightstack**2, axis=(1, 2)) spread = (GWp / (PWp + (PWp == 0)) - GWP / (PWP + (PWP == 0))) dspread = numpy.sqrt((PWp**2 * GWG + GWp**2 * PWP - 2 * GWp * PWp * GWP) / (PWp + (PWp == 0))**4) return spread, dspread
def fit_im(im, psf, weight=None, dq=None, psfderiv=True, nskyx=0, nskyy=0, refit_psf=False, fixedstars=None, verbose=False, miniter=4, maxiter=10, blist=None): if fixedstars is not None and len(fixedstars['x']) > 0: fixedpsflist = {'psfob': fixedstars['psfob'], 'ind': fixedstars['psf']} fixedmodel = build_model(fixedstars['x'], fixedstars['y'], fixedstars['flux'], im.shape[0], im.shape[1], psflist=fixedpsflist, offset=fixedstars['offset']) else: fixedmodel = numpy.zeros_like(im) if isinstance(weight, int): weight = numpy.ones_like(im)*weight im = im model = numpy.zeros_like(im)+fixedmodel xa = numpy.zeros(0, dtype='f4') ya = xa.copy() lsky = numpy.median(im[weight > 0]) hsky = numpy.median(im[weight > 0]) msky = 0 passno = numpy.zeros(0, dtype='i4') guessflux, guesssky = None, None titer = -1 lastiter = -1 roughfwhm = psfmod.neff_fwhm(psf(im.shape[0]//2, im.shape[1]//2)) roughfwhm = numpy.max([roughfwhm, 3.]) while True: titer += 1 hsky = sky_im(im-model, weight=weight, npix=20) lsky = sky_im(im-model, weight=weight, npix=10*roughfwhm) if titer != lastiter: # in first passes, do not split sources! blendthresh = 2 if titer < 2 else 0.2 xn, yn = peakfind(im-model-hsky, model-msky, weight, dq, psf, keepsat=(titer == 0), blendthreshhold=blendthresh) if len(xa) > 0 and len(xn) > 0: keep = neighbor_dist(xn, yn, xa, ya) > 1.5 xn, yn = (c[keep] for c in (xn, yn)) if (titer == 0) and (blist is not None): xnb, ynb = add_bright_stars(xn, yn, blist, im) xn = numpy.concatenate([xn, xnb]).astype('f4') yn = numpy.concatenate([yn, ynb]).astype('f4') xa, ya = (numpy.concatenate([xa, xn]).astype('f4'), numpy.concatenate([ya, yn]).astype('f4')) passno = numpy.concatenate([passno, numpy.zeros(len(xn))+titer]) if verbose: print('Iteration %d, found %d sources.' % (titer+1, len(xn))) else: xn, yn = numpy.zeros(0, dtype='f4'), numpy.zeros(0, dtype='f4') if titer != lastiter: if (titer == maxiter-1) or ( (titer >= miniter-1) and (len(xn) < 100)) or ( len(xa) > 40000): lastiter = titer + 1 sz = get_sizes(xa, ya, im-hsky, weight=weight, blist=blist) if guessflux is not None: guess = numpy.concatenate([guessflux, numpy.zeros_like(xn), guesssky]) else: guess = None sky = hsky if titer >= 2 else lsky # in final iteration, no longer allow shifting locations; just fit # centroids. tpsfderiv = psfderiv if lastiter != titer else False psfs = build_psf_list(xa, ya, psf, sz, psfderiv=tpsfderiv) flux, model, msky = fit_once(im-sky, xa, ya, psfs, psfderiv=tpsfderiv, weight=weight, guess=guess, nskyx=nskyx, nskyy=nskyy) model += fixedmodel centroids = compute_centroids(xa, ya, psfs, flux[0], im-(sky+msky), im-model-sky, weight) xcen, ycen, stamps = centroids if titer == lastiter: tflux, tskypar = unpack_fitpar(flux[0], len(xa), False) stats = compute_stats(xa-numpy.round(xa), ya-numpy.round(ya), stamps[0], stamps[2], stamps[3], stamps[1], tflux) stats['flags'] = extract_im(xa, ya, dq).astype('i4') stats['sky'] = extract_im(xa, ya, sky+msky).astype('f4') break guessflux, guesssky = unpack_fitpar(flux[0], len(xa), psfderiv) if refit_psf and len(xa) > 0: # how far the centroids of the model PSFs would # be from (0, 0) if instantiated there # this initial definition includes the known offset (since # we instantiated off a pixel center), and the model offset xe, ye = psfmod.simple_centroid( psfmod.central_stamp(stamps[4], censize=stamps[0].shape[-1])) # now we subtract the known offset xe -= xa-numpy.round(xa) ye -= ya-numpy.round(ya) if hasattr(psf, 'fitfun'): psffitfun = psf.fitfun npsf = psffitfun(xa, ya, xcen+xe, ycen+ye, stamps[0], stamps[1], stamps[2], stamps[3], nkeep=200) if npsf is not None: npsf.fitfun = psffitfun else: shiftx = xcen + xe + xa - numpy.round(xa) shifty = ycen + ye + ya - numpy.round(ya) npsf = find_psf(xa, shiftx, ya, shifty, stamps[0], stamps[3], stamps[1]) # we removed the centroid offset of the model PSFs; # we need to correct the positions to compensate xa += xe ya += ye psf = npsf xcen, ycen = (numpy.clip(c, -3, 3) for c in (xcen, ycen)) xa, ya = (numpy.clip(c, -0.499, s-0.501) for c, s in zip((xa+xcen, ya+ycen), im.shape)) fluxunc = numpy.sum(stamps[2]**2.*stamps[3]**2., axis=(1, 2)) fluxunc = fluxunc + (fluxunc == 0)*1e-20 fluxunc = (fluxunc**(-0.5)).astype('f4') # for very bright stars, fluxunc is unreliable because the entire # (small) stamp is saturated. # these stars all have very bright inferred fluxes # i.e., 50k saturates, so we can cut there. keep = (((guessflux/fluxunc > 3) | (guessflux > 1e5)) & cull_near(xa, ya, guessflux)) xa, ya = (c[keep] for c in (xa, ya)) passno = passno[keep] guessflux = guessflux[keep] # should probably also subtract these stars from the model image # which is used for peak finding. But the faint stars should # make little difference? if fixedmodel is not None: model += fixedmodel flux, skypar = unpack_fitpar(flux[0], len(xa), False) stars = OrderedDict([('x', xa), ('y', ya), ('flux', flux)] + [(f, stats[f]) for f in stats]) res = (stars, skypar, model+sky, sky+msky, psf) return res