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
def find_psf(xcen, shiftx, ycen, shifty, psfstack, weightstack, imstack, stampsz=59, nkeep=100): """Find PSF from stamps.""" # let's just go ahead and correlate the noise xr = numpy.round(shiftx) yr = numpy.round(shifty) psfqf = (numpy.sum(psfstack * (weightstack > 0), axis=(1, 2)) / numpy.sum(psfstack, axis=(1, 2))) totalflux = numpy.sum(psfstack, axis=(1, 2)) timflux = numpy.sum(imstack, axis=(1, 2)) toneflux = numpy.sum(psfstack, axis=(1, 2)) tmedflux = numpy.median(psfstack, axis=(1, 2)) tfracflux = toneflux / numpy.clip(timflux, 100, numpy.inf) tfracflux2 = ( (toneflux - tmedflux * psfstack.shape[1] * psfstack.shape[2]) / numpy.clip(timflux, 100, numpy.inf)) okpsf = ((numpy.abs(psfqf - 1) < 0.03) & (tfracflux > 0.5) & (tfracflux2 > 0.2)) if numpy.sum(okpsf) > 0: shiftxm = numpy.median(shiftx[okpsf]) shiftym = numpy.median(shifty[okpsf]) okpsf = (okpsf & (numpy.abs(shiftx - shiftxm) < 1.) & (numpy.abs(shifty - shiftym) < 1.)) if numpy.sum(okpsf) <= 5: print('Fewer than 5 stars accepted in image, keeping original PSF') return None if numpy.sum(okpsf) > nkeep: okpsf = okpsf & (totalflux > -numpy.sort(-totalflux[okpsf])[nkeep - 1]) psfstack = psfstack[okpsf, :, :] weightstack = weightstack[okpsf, :, :] totalflux = totalflux[okpsf] xcen = xcen[okpsf] ycen = ycen[okpsf] shiftx = shiftx[okpsf] shifty = shifty[okpsf] for i in range(psfstack.shape[0]): psfstack[i, :, :] = shift(psfstack[i, :, :], [-shiftx[i], -shifty[i]]) if (numpy.abs(xr[i]) > 0) or (numpy.abs(yr[i]) > 0): weightstack[i, :, :] = shift(weightstack[i, :, :], [-xr[i], -yr[i]], mode='constant', cval=0.) # our best guess as to the PSFs & their weights # select some reasonable sample of the PSFs totalflux = numpy.sum(psfstack, axis=(1, 2)) psfstack /= totalflux.reshape(-1, 1, 1) weightstack *= totalflux.reshape(-1, 1, 1) tpsf = numpy.median(psfstack, axis=0) tpsf = psfmod.center_psf(tpsf) if tpsf.shape == stampsz: return tpsf xc = numpy.arange(tpsf.shape[0]).reshape(-1, 1) - tpsf.shape[0] // 2 yc = xc.reshape(1, -1) rc = numpy.sqrt(xc**2. + yc**2.) stampszo2 = psfstack[0].shape[0] // 2 wt = numpy.clip((stampszo2 + 1 - rc) / 4., 0., 1.) overlap = (wt != 1) & (wt != 0) def objective(par): mod = psfmod.moffat_psf(par[0], beta=2.5, xy=par[2], yy=par[3], deriv=False, stampsz=tpsf.shape[0]) mod /= numpy.sum(mod) return ((tpsf - mod)[overlap]).reshape(-1) from scipy.optimize import leastsq par = leastsq(objective, [4., 3., 0., 1.])[0] modpsf = psfmod.moffat_psf(par[0], beta=2.5, xy=par[2], yy=par[3], deriv=False, stampsz=stampsz) modpsf /= numpy.sum(psfmod.central_stamp(modpsf)) npsf = modpsf.copy() npsfcen = psfmod.central_stamp(npsf, tpsf.shape[0]) npsfcen[:, :] = tpsf * wt + (1 - wt) * npsfcen[:, :] npsf /= numpy.sum(npsf) return psfmod.SimplePSF(npsf, normalize=-1)
def compute_centroids(x, y, psflist, flux, im, resid, weight): # define c = integral(x * I * P * W) / integral(I * P * W) # x = x/y coordinate, I = isolated stamp, P = PSF model, W = weight # Assuming I ~ P(x-y) for some small offset y and expanding, # integrating by parts gives: # y = 2 / integral(P*P*W) * integral(x*(I-P)*W) # that is the offset we want. # we want to compute the centroids on the image after the other sources # have been subtracted off. # we construct this image by taking the residual image, and then # star-by-star adding the model back. centroidsize = 19 psfs = [numpy.zeros((len(x), centroidsize, centroidsize), dtype='f4') for i in range(len(psflist))] for j in range(len(psflist)): for i in range(len(x)): psfs[j][i, :, :] = psfmod.central_stamp(psflist[j][i], censize=centroidsize) stampsz = psfs[0].shape[-1] stampszo2 = (stampsz-1)/2 dx = numpy.arange(stampsz, dtype='i4')-stampszo2 dx = dx.reshape(-1, 1) dy = dx.copy().reshape(1, -1) xp = numpy.round(x).astype('i4') yp = numpy.round(y).astype('i4') # subtracting to get to the edge of the stamp, adding back to deal with # the padded image. xe = xp - stampszo2 + stampszo2 ye = yp - stampszo2 + stampszo2 resid = numpy.pad(resid, [stampszo2, stampszo2], constant_values=0., mode='constant') weight = numpy.pad(weight, [stampszo2, stampszo2], constant_values=0., mode='constant') im = numpy.pad(im, [stampszo2, stampszo2], constant_values=0., mode='constant') repeat = len(psflist) residst = numpy.array([resid[xe0:xe0+stampsz, ye0:ye0+stampsz] for (xe0, ye0) in zip(xe, ye)]) weightst = numpy.array([weight[xe0:xe0+stampsz, ye0:ye0+stampsz] for (xe0, ye0) in zip(xe, ye)]) psfst = psfs[0] * flux[:len(x)*repeat:repeat].reshape(-1, 1, 1) imst = numpy.array([im[xe0:xe0+stampsz, ye0:ye0+stampsz] for (xe0, ye0) in zip(xe, ye)]) if len(x) == 0: weightst = psfs[0].copy() residst = psfs[0].copy() imst = psfs[0].copy() modelst = psfst.copy() if len(psflist) > 1: modelst += psfs[1]*flux[1:len(x)*repeat:repeat].reshape(-1, 1, 1) modelst += psfs[2]*flux[2:len(x)*repeat:repeat].reshape(-1, 1, 1) cen = [] ppw = numpy.sum(modelst*modelst*weightst, axis=(1, 2)) pp = numpy.sum(modelst*modelst, axis=(1, 2)) for dc in (dx, dy): xrpw = numpy.sum(dc[None, :, :]*residst*modelst*weightst, axis=(1, 2)) xmmpm = numpy.sum(dc[None, :, :]*(modelst-psfst)*modelst, axis=(1, 2)) cen.append(2*xrpw/(ppw + (ppw == 0.))*(ppw != 0.) + 2*xmmpm/(pp + (pp == 0.))*(pp != 0.)) xcen, ycen = cen norm = numpy.sum(modelst, axis=(1, 2)) norm = norm + (norm == 0) psfqf = numpy.sum(modelst*(weightst > 0), axis=(1, 2)) / norm m = psfqf < 0.5 xcen[m] = 0. ycen[m] = 0. if (len(psflist) > 1) and numpy.sum(m) > 0: ind = numpy.flatnonzero(m) # just use the derivative-based centroids for this case. fluxnz = flux[repeat*ind] fluxnz = fluxnz + (fluxnz == 0) xcen[ind] = flux[repeat*ind+1]/fluxnz ycen[ind] = flux[repeat*ind+2]/fluxnz # stamps: 0: neighbor-subtracted images, # 1: images, # 2: psfs with shifts # 3: psfs without shifts res = (xcen, ycen, (modelst+residst, imst, modelst, weightst, psfst)) return res
def compute_centroids(x, y, psflist, flux, im, resid, weight, derivcentroids=False): # define c = integral(x * I * P * W) / integral(I * P * W) # x = x/y coordinate, I = isolated stamp, P = PSF model, W = weight # Assuming I ~ P(x-y) for some small offset y and expanding, # integrating by parts gives: # y = 2 / integral(P*P*W) * integral(x*(I-P)*W) # that is the offset we want. # we want to compute the centroids on the image after the other sources # have been subtracted off. # we construct this image by taking the residual image, and then # star-by-star adding the model back. centroidsize = 19 psfs = [ numpy.zeros((len(x), centroidsize, centroidsize), dtype='f4') for i in range(len(psflist)) ] for j in range(len(psflist)): for i in range(len(x)): psfs[j][i, :, :] = psfmod.central_stamp(psflist[j][i], censize=centroidsize) stampsz = psfs[0].shape[-1] stampszo2 = (stampsz - 1) / 2 dx = numpy.arange(stampsz, dtype='i4') - stampszo2 dx = dx.reshape(-1, 1) dy = dx.copy().reshape(1, -1) xp = numpy.round(x).astype('i4') yp = numpy.round(y).astype('i4') # subtracting to get to the edge of the stamp, adding back to deal with # the padded image. xe = xp - stampszo2 + stampszo2 ye = yp - stampszo2 + stampszo2 resid = numpy.pad(resid, [stampszo2, stampszo2], constant_values=0., mode='constant') weight = numpy.pad(weight, [stampszo2, stampszo2], constant_values=0., mode='constant') im = numpy.pad(im, [stampszo2, stampszo2], constant_values=0., mode='constant') repeat = len(psflist) residst = numpy.array([ resid[xe0:xe0 + stampsz, ye0:ye0 + stampsz] for (xe0, ye0) in zip(xe, ye) ]) weightst = numpy.array([ weight[xe0:xe0 + stampsz, ye0:ye0 + stampsz] for (xe0, ye0) in zip(xe, ye) ]) psfst = psfs[0] * flux[:len(x) * repeat:repeat].reshape(-1, 1, 1) imst = numpy.array([ im[xe0:xe0 + stampsz, ye0:ye0 + stampsz] for (xe0, ye0) in zip(xe, ye) ]) if len(x) == 0: weightst = psfs[0].copy() residst = psfs[0].copy() imst = psfs[0].copy() modelst = psfst.copy() if len(psflist) > 1: modelst += psfs[1] * flux[1:len(x) * repeat:repeat].reshape(-1, 1, 1) modelst += psfs[2] * flux[2:len(x) * repeat:repeat].reshape(-1, 1, 1) cen = [] ppw = numpy.sum(modelst * modelst * weightst, axis=(1, 2)) pp = numpy.sum(modelst * modelst, axis=(1, 2)) for dc in (dx, dy): xrpw = numpy.sum(dc[None, :, :] * residst * modelst * weightst, axis=(1, 2)) xmmpm = numpy.sum(dc[None, :, :] * (modelst - psfst) * modelst, axis=(1, 2)) cen.append(2 * xrpw / (ppw + (ppw == 0.)) * (ppw != 0.) + 2 * xmmpm / (pp + (pp == 0.)) * (pp != 0.)) xcen, ycen = cen norm = numpy.sum(modelst, axis=(1, 2)) norm = norm + (norm == 0) psfqf = numpy.sum(modelst * (weightst > 0), axis=(1, 2)) / norm # how should we really be doing this? derivcentroids is the first order # approximation to the right thing. the centroid computation that I do # otherwise should be unbiased but noisier than optimal for significantly # offset peaks. Vakili, Hogg (2016) say that I should convolve with the # PSF and interpolate to the brightest point with some polynomial. I # expected this to be slow (convolving thousands of stamps individually # with the PSF each iteration), but the spread_model code worked pretty # well, so this is probably a worthwhile thing to try. if it worked, it # would obviate some of the code mess above, and be optimal, so that # sounds worthwhile. if not derivcentroids: m = psfqf < 0.5 else: m = numpy.ones(len(xcen), dtype='bool') xcen[m] = 0. ycen[m] = 0. if (len(psflist) > 1) and numpy.sum(m) > 0: ind = numpy.flatnonzero(m) # just use the derivative-based centroids for this case. fluxnz = flux[repeat * ind] fluxnz = fluxnz + (fluxnz == 0) xcen[ind] = flux[repeat * ind + 1] / fluxnz ycen[ind] = flux[repeat * ind + 2] / fluxnz # stamps: 0: neighbor-subtracted images, # 1: images, # 2: psfs with shifts # 3: psfs without shifts res = (xcen, ycen, (modelst + residst, imst, modelst, weightst, psfst)) return res