576 Fit a PSF + smooth background model (linear) to a small region
580 fmask: the Mask plane for pixels in the Footprint
581 pk: the Peak within the Footprint that we are going to fit a PSF model for
582 pkF: the floating-point coordinates of this peak
583 pkres: a PerPeak object that will hold the results for this peak
584 fbb: the bounding-box of this Footprint (a Box2I)
585 peaks: a list of all the Peak objects within this Footprint
586 peaksF: a python list of the floating-point (x,y) peak locations
589 psffwhm: FWHM of the PSF, in pixels
590 img: umm, the Image in which this Footprint finds itself
591 varimg: Variance plane
592 psfChisqCut*: floats: cuts in chisq-per-pixel at which to accept the PSF model
602 R0 = int(math.ceil(psffwhm * 1.))
604 R1 = int(math.ceil(psffwhm * 1.5))
605 cx,cy = pkF.getX(), pkF.getY()
606 psfimg = psf.computeImage(cx, cy)
609 R2 = R1 +
min(psfimg.getWidth(), psfimg.getHeight())/2.
611 pbb = psfimg.getBBox()
613 px0,py0 = psfimg.getX0(), psfimg.getY0()
616 xlo = int(math.floor(cx - R1))
617 ylo = int(math.floor(cy - R1))
618 xhi = int(math.ceil (cx + R1))
619 yhi = int(math.ceil (cy + R1))
622 xlo,xhi = stampbb.getMinX(), stampbb.getMaxX()
623 ylo,yhi = stampbb.getMinY(), stampbb.getMaxY()
624 if xlo > xhi
or ylo > yhi:
625 log.logdebug(
'Skipping this peak: out of bounds')
626 pkres.setOutOfBounds()
630 if tinyFootprintSize>0
and (((xhi - xlo) < tinyFootprintSize)
or
631 ((yhi - ylo) < tinyFootprintSize)):
632 log.logdebug(
'Skipping this peak: tiny footprint / close to edge')
633 pkres.setTinyFootprint()
638 for pk2,pkF2
in zip(peaks, peaksF):
641 if pkF.distanceSquared(pkF2) > R2**2:
643 opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
644 if not opsfimg.getBBox(afwImage.LOCAL).overlaps(stampbb):
646 otherpeaks.append(opsfimg)
647 log.logdebug(
'%i other peaks within range' % len(otherpeaks))
654 NT1 = 4 + len(otherpeaks)
658 NP = (1 + yhi - ylo) * (1 + xhi - xlo)
671 ix0,iy0 = img.getX0(), img.getY0()
672 fx0,fy0 = fbb.getMinX(), fbb.getMinY()
673 fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
674 islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
675 fmask_sub = fmask .getArray()[fslice]
676 var_sub = varimg.getArray()[islice]
677 img_sub = img .getArray()[islice]
680 psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
681 pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
682 px0,px1 = pbb.getMinX(), pbb.getMaxX()
683 py0,py1 = pbb.getMinY(), pbb.getMaxY()
686 valid = (fmask_sub > 0)
687 xx,yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
688 RR = ((xx - cx)**2)[np.newaxis,:] + ((yy - cy)**2)[:,np.newaxis]
689 valid *= (RR <= R1**2)
690 valid *= (var_sub > 0)
694 log.warn(
'Skipping peak at (%.1f,%.1f): no unmasked pixels nearby'
696 pkres.setNoValidPixels()
700 XX,YY = np.meshgrid(xx, yy)
701 ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
703 inpsfx = (xx >= px0) * (xx <= px1)
704 inpsfy = (yy >= py0) * (yy <= py1)
705 inpsf = np.outer(inpsfy, inpsfx)
706 indx = np.outer(inpsfy, (xx > px0) * (xx < px1))
707 indy = np.outer((yy > py0) * (yy < py1), inpsfx)
712 def _overlap(xlo, xhi, xmin, xmax):
713 assert((xlo <= xmax)
and (xhi >= xmin)
and
714 (xlo <= xhi)
and (xmin <= xmax))
715 xloclamp =
max(xlo, xmin)
717 xhiclamp =
min(xhi, xmax)
718 Xhi = Xlo + (xhiclamp - xloclamp)
719 assert(xloclamp >= 0)
721 return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
723 A = np.zeros((NP, NT2))
727 A[:,I_sky_ramp_x] = ipixes[:,0] + (xlo-cx)
728 A[:,I_sky_ramp_y] = ipixes[:,1] + (ylo-cy)
731 px0,px1 = pbb.getMinX(), pbb.getMaxX()
732 py0,py1 = pbb.getMinY(), pbb.getMaxY()
733 sx1,sx2,sx3,sx4 = _overlap(xlo, xhi, px0, px1)
734 sy1,sy2,sy3,sy4 = _overlap(ylo, yhi, py0, py1)
735 dpx0,dpy0 = px0 - xlo, py0 - ylo
736 psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
737 psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
738 psfsub = psfarr[psf_y_slice, psf_x_slice]
739 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
740 A[inpsf[valid], I_psf] = psfsub[vsub]
744 oldsx = (sx1,sx2,sx3,sx4)
745 sx1,sx2,sx3,sx4 = _overlap(xlo, xhi, px0+1, px1-1)
746 psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1] -
747 psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1]) / 2.
748 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
749 A[indx[valid], I_dx] = psfsub[vsub]
751 (sx1,sx2,sx3,sx4) = oldsx
754 sy1,sy2,sy3,sy4 = _overlap(ylo, yhi, py0+1, py1-1)
755 psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice] -
756 psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice]) / 2.
757 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
758 A[indy[valid], I_dy] = psfsub[vsub]
761 for j,opsf
in enumerate(otherpeaks):
763 ino = np.outer((yy >= obb.getMinY()) * (yy <= obb.getMaxY()),
764 (xx >= obb.getMinX()) * (xx <= obb.getMaxX()))
765 dpx0,dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
766 sx1,sx2,sx3,sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
767 sy1,sy2,sy3,sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
768 opsfarr = opsf.getArray()
769 psfsub = opsfarr[sy3 - dpy0 : sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
770 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
771 A[ino[valid], I_opsf + j] = psfsub[vsub]
777 rw = np.ones_like(RR)
780 rw[ii] = np.maximum(0, 1. - ((rr - R0) / (R1 - R0)))
781 w = np.sqrt(rw[valid] / var_sub[valid])
783 sumr = np.sum(rw[valid])
784 log.log(-9,
'sumr = %g' % sumr)
788 Aw = A * w[:,np.newaxis]
797 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
798 im1[ipixes[:,1], ipixes[:,0]] = A[:, i]
799 plt.subplot(R, C, i+1)
800 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
801 plt.subplot(R, C, NT2+1)
802 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
803 im1[ipixes[:,1], ipixes[:,0]] = b
804 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
805 plt.subplot(R, C, NT2+2)
806 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
807 im1[ipixes[:,1], ipixes[:,0]] = w
808 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
820 X1,r1,rank1,s1 = np.linalg.lstsq(Aw[:,:NT1], bw)
822 X2,r2,rank2,s2 = np.linalg.lstsq(Aw, bw)
823 except np.linalg.LinAlgError, e:
824 log.log(log.WARN,
"Failed to fit PSF to child: %s" % e)
825 pkres.setPsfFitFailed()
828 log.log(-9,
'r1 r2 %g %g' % (r1, r2))
840 dof1 = sumr - len(X1)
841 dof2 = sumr - len(X2)
842 log.log(-9,
'dof1, dof2 %g %g' % (dof1, dof2))
845 if dof1 <= 0
or dof2 <= 0:
846 log.logdebug(
'Skipping this peak: bad DOF %g, %g' % (dof1, dof2))
852 log.logdebug(
'PSF fits: chisq/dof = %g, %g' % (q1,q2))
853 ispsf1 = (q1 < psfChisqCut1)
854 ispsf2 = (q2 < psfChisqCut2)
856 pkres.psfFit1 = (chisq1, dof1)
857 pkres.psfFit2 = (chisq2, dof2)
861 fdx, fdy = X2[I_dx], X2[I_dy]
866 ispsf2 = ispsf2
and (abs(dx) < 1.
and abs(dy) < 1.)
867 log.logdebug(
'isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s' %
868 (dx,dy, str(ispsf2)))
870 pkres.psfFitBigDecenter =
True
875 psfimg2 = psf.computeImage(cx + dx, cy + dy)
877 pbb2 = psfimg2.getBBox()
880 px0,py0 = psfimg2.getX0(), psfimg2.getY0()
881 psfarr = psfimg2.getArray()[pbb2.getMinY()-py0: 1+pbb2.getMaxY()-py0,
882 pbb2.getMinX()-px0: 1+pbb2.getMaxX()-px0]
883 px0,py0 = pbb2.getMinX(), pbb2.getMinY()
884 px1,py1 = pbb2.getMaxX(), pbb2.getMaxY()
889 sx1,sx2,sx3,sx4 = _overlap(xlo, xhi, px0, px1)
890 sy1,sy2,sy3,sy4 = _overlap(ylo, yhi, py0, py1)
891 dpx0,dpy0 = px0 - xlo, py0 - ylo
892 psfsub = psfarr[sy3 - dpy0 : sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
893 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
894 xx,yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
895 inpsf = np.outer((yy >= py0) * (yy <= py1), (xx >= px0) * (xx <= px1))
896 Ab[inpsf[valid], I_psf] = psfsub[vsub]
898 Aw = Ab * w[:,np.newaxis]
900 Xb,rb,rankb,sb = np.linalg.lstsq(Aw, bw)
905 dofb = sumr - len(Xb)
906 log.log(-9,
'rb, dofb %g %g' %(rb, dofb))
908 ispsf2 = (qb < psfChisqCut2b)
911 log.logdebug(
'shifted PSF: new chisq/dof = %g; good? %s' %
913 pkres.psfFit3 = (chisqb, dofb)
916 if (((ispsf1
and ispsf2)
and (q2 < q1))
or
917 (ispsf2
and not ispsf1)):
921 log.log(-9,
'dof %g' % dof)
922 log.logdebug(
'Keeping shifted-PSF model')
925 pkres.psfFitWithDecenter =
True
931 log.log(-9,
'dof %g' % dof)
932 log.logdebug(
'Keeping unshifted PSF model')
934 ispsf = (ispsf1
or ispsf2)
938 SW,SH = 1+xhi-xlo, 1+yhi-ylo
939 psfmod = afwImage.ImageF(SW,SH)
940 psfmod.setXY0(xlo,ylo)
941 psfderivmodm = afwImage.MaskedImageF(SW,SH)
942 psfderivmod = psfderivmodm.getImage()
943 psfderivmod.setXY0(xlo,ylo)
944 model = afwImage.ImageF(SW,SH)
945 model.setXY0(xlo,ylo)
946 for i
in range(len(Xpsf)):
947 for (x,y),v
in zip(ipixes, A[:,i]*Xpsf[i]):
948 ix,iy = int(x),int(y)
949 model.set(ix, iy, model.get(ix,iy) + float(v))
950 if i
in [I_psf, I_dx, I_dy]:
951 psfderivmod.set(ix, iy, psfderivmod.get(ix,iy) + float(v))
954 psfmod.set(int(x),int(y), float(A[ii, I_psf] * Xpsf[I_psf]))
957 modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
960 pkres.psfFitDebugPsf0Img = psfimg
961 pkres.psfFitDebugPsfImg = psfmod
962 pkres.psfFitDebugPsfDerivImg = psfderivmod
963 pkres.psfFitDebugPsfModel = model
964 pkres.psfFitDebugStamp = img.Factory(img, stampbb,
True)
965 pkres.psfFitDebugValidPix = valid
966 pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb,
True)
967 ww = np.zeros(valid.shape, np.float)
969 pkres.psfFitDebugWeight = ww
970 pkres.psfFitDebugRampWeight = rw
977 pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
978 pkres.psfFitCenter = (cx,cy)
979 log.log(-9,
'saving chisq,dof %g %g' % (chisq, dof))
980 pkres.psfFitBest = (chisq, dof)
981 pkres.psfFitParams = Xpsf
982 pkres.psfFitFlux = Xpsf[I_psf]
983 pkres.psfFitNOthers = len(otherpeaks)
986 pkres.setDeblendedAsPsf()
990 log.logdebug(
'Deblending as PSF; setting template to PSF model')
993 psfimg = psf.computeImage(cx, cy)
995 psfimg *= Xpsf[I_psf]
996 psfimg = psfimg.convertF()
1000 psfbb = psfimg.getBBox()
1001 fpcopy.clipTo(psfbb)
1002 bb = fpcopy.getBBox()
1005 psfmod = afwImage.MaskedImageF(bb)
1006 afwDet.copyWithinFootprintImage(fpcopy, psfimg, psfmod.getImage())
1008 fpcopy.clipToNonzero(psfmod.getImage())
1010 pkres.setTemplate(psfmod, fpcopy)
1013 pkres.setPsfTemplate(psfmod, fpcopy)