LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
Classes | Functions
lsst.meas.deblender.baseline Namespace Reference

Classes

class  PerFootprint
 
class  PerPeak
 
class  CachingPsf
 

Functions

def deblend
 
def _fitPsfs
 
def _fitPsf
 
def _handle_flux_at_edge
 

Function Documentation

def lsst.meas.deblender.baseline._fitPsf (   fp,
  fmask,
  pk,
  pkF,
  pkres,
  fbb,
  peaks,
  peaksF,
  log,
  psf,
  psffwhm,
  img,
  varimg,
  psfChisqCut1,
  psfChisqCut2,
  psfChisqCut2b,
  tinyFootprintSize = 2 
)
private
Fit a PSF + smooth background model (linear) to a small region
around the peak.

fp: Footprint
fmask: the Mask plane for pixels in the Footprint
pk: the Peak within the Footprint that we are going to fit a PSF model for
pkF: the floating-point coordinates of this peak
pkres: a PerPeak object that will hold the results for this peak
fbb: the bounding-box of this Footprint (a Box2I)
peaks: a list of all the Peak objects within this Footprint
peaksF: a python list of the floating-point (x,y) peak locations
log: pex Log object
psf: afw PSF object
psffwhm: FWHM of the PSF, in pixels
img: umm, the Image in which this Footprint finds itself
varimg: Variance plane
psfChisqCut*: floats: cuts in chisq-per-pixel at which to accept the PSF model

Definition at line 573 of file baseline.py.

574  ):
575  '''
576  Fit a PSF + smooth background model (linear) to a small region
577  around the peak.
578 
579  fp: Footprint
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
587  log: pex Log object
588  psf: afw PSF object
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
593 
594  '''
595  import lsstDebug
596  # my __name__ is lsst.meas.deblender.baseline
597  debugPlots = lsstDebug.Info(__name__).plots
598  debugPsf = lsstDebug.Info(__name__).psf
599 
600  # The small region is a disk out to R0, plus a ramp with
601  # decreasing weight down to R1.
602  R0 = int(math.ceil(psffwhm * 1.))
603  # ramp down to zero weight at this radius...
604  R1 = int(math.ceil(psffwhm * 1.5))
605  cx,cy = pkF.getX(), pkF.getY()
606  psfimg = psf.computeImage(cx, cy)
607  # R2: distance to neighbouring peak in order to put it
608  # into the model
609  R2 = R1 + min(psfimg.getWidth(), psfimg.getHeight())/2.
610 
611  pbb = psfimg.getBBox()
612  pbb.clip(fbb)
613  px0,py0 = psfimg.getX0(), psfimg.getY0()
614 
615  # The bounding-box of the local region we are going to fit ("stamp")
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))
620  stampbb = afwGeom.Box2I(afwGeom.Point2I(xlo,ylo), afwGeom.Point2I(xhi,yhi))
621  stampbb.clip(fbb)
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()
627  return
628 
629  # drop tiny footprints too?
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()
634  return
635 
636  # find other peaks within range...
637  otherpeaks = []
638  for pk2,pkF2 in zip(peaks, peaksF):
639  if pk2 == pk:
640  continue
641  if pkF.distanceSquared(pkF2) > R2**2:
642  continue
643  opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
644  if not opsfimg.getBBox(afwImage.LOCAL).overlaps(stampbb):
645  continue
646  otherpeaks.append(opsfimg)
647  log.logdebug('%i other peaks within range' % len(otherpeaks))
648 
649  # Now we are going to do a least-squares fit for the flux
650  # in this PSF, plus a decenter term, a linear sky, and
651  # fluxes of nearby sources (assumed point sources). Build
652  # up the matrix...
653  # Number of terms -- PSF flux, constant sky, X, Y, + other PSF fluxes
654  NT1 = 4 + len(otherpeaks)
655  # + PSF dx, dy
656  NT2 = NT1 + 2
657  # Number of pixels -- at most
658  NP = (1 + yhi - ylo) * (1 + xhi - xlo)
659  # indices of columns in the "A" matrix.
660  I_psf = 0
661  I_sky = 1
662  I_sky_ramp_x = 2
663  I_sky_ramp_y = 3
664  # offset of other psf fluxes:
665  I_opsf = 4
666  I_dx = NT1 + 0
667  I_dy = NT1 + 1
668 
669  # Build the matrix "A", rhs "b" and weight "w".
670 
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]
678 
679  # Clip the PSF image to match its bbox
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()
684 
685  # Compute the "valid" pixels within our region-of-interest
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)
691  NP = valid.sum()
692 
693  if NP == 0:
694  log.warn('Skipping peak at (%.1f,%.1f): no unmasked pixels nearby'
695  % (cx,cy))
696  pkres.setNoValidPixels()
697  return
698 
699  # pixel coords of valid pixels
700  XX,YY = np.meshgrid(xx, yy)
701  ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
702 
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)
708 
709  del inpsfx
710  del inpsfy
711 
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)
716  Xlo = xloclamp - xlo
717  xhiclamp = min(xhi, xmax)
718  Xhi = Xlo + (xhiclamp - xloclamp)
719  assert(xloclamp >= 0)
720  assert(Xlo >= 0)
721  return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
722 
723  A = np.zeros((NP, NT2))
724  # Constant term
725  A[:,I_sky] = 1.
726  # Sky slope terms: dx, dy
727  A[:,I_sky_ramp_x] = ipixes[:,0] + (xlo-cx)
728  A[:,I_sky_ramp_y] = ipixes[:,1] + (ylo-cy)
729 
730  # whew, grab the valid overlapping PSF pixels
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]
741 
742  # PSF dx -- by taking the half-difference of shifted-by-one and
743  # shifted-by-minus-one.
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]
750  # revert x indices...
751  (sx1,sx2,sx3,sx4) = oldsx
752 
753  # PSF dy
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]
759 
760  # other PSFs...
761  for j,opsf in enumerate(otherpeaks):
762  obb = opsf.getBBox()
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]
772 
773  b = img_sub[valid]
774 
775  # Weights -- from ramp and image variance map.
776  # ramp weights -- from 1 at R0 down to 0 at R1.
777  rw = np.ones_like(RR)
778  ii = (RR > R0**2)
779  rr = np.sqrt(RR[ii])
780  rw[ii] = np.maximum(0, 1. - ((rr - R0) / (R1 - R0)))
781  w = np.sqrt(rw[valid] / var_sub[valid])
782  # save the effective number of pixels
783  sumr = np.sum(rw[valid])
784  log.log(-9, 'sumr = %g' % sumr)
785 
786  del ii
787 
788  Aw = A * w[:,np.newaxis]
789  bw = b * w
790 
791  if debugPlots:
792  import pylab as plt
793  plt.clf()
794  N = NT2 + 2
795  R,C = 2, (N+1) / 2
796  for i in range(NT2):
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')
809  plt.savefig('A.png')
810 
811  # We do fits with and without the decenter (dx,dy) terms.
812  # Since the dx,dy terms are at the end of the matrix,
813  # we can do that just by trimming off those elements.
814  #
815  # The SVD can fail if there are NaNs in the matrices; this should
816  # really be handled upstream
817  try:
818  # NT1 is number of terms without dx,dy;
819  # X1 is the result without decenter
820  X1,r1,rank1,s1 = np.linalg.lstsq(Aw[:,:NT1], bw)
821  # X2 is with decenter
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()
826  return
827 
828  log.log(-9, 'r1 r2 %g %g' % (r1, r2))
829 
830  # r is weighted chi-squared = sum over pixels: ramp * (model -
831  # data)**2/sigma**2
832  if len(r1) > 0:
833  chisq1 = r1[0]
834  else:
835  chisq1 = 1e30
836  if len(r2) > 0:
837  chisq2 = r2[0]
838  else:
839  chisq2 = 1e30
840  dof1 = sumr - len(X1)
841  dof2 = sumr - len(X2)
842  log.log(-9, 'dof1, dof2 %g %g' % (dof1, dof2))
843 
844  # This can happen if we're very close to the edge (?)
845  if dof1 <= 0 or dof2 <= 0:
846  log.logdebug('Skipping this peak: bad DOF %g, %g' % (dof1, dof2))
847  pkres.setBadPsfDof()
848  return
849 
850  q1 = chisq1/dof1
851  q2 = chisq2/dof2
852  log.logdebug('PSF fits: chisq/dof = %g, %g' % (q1,q2))
853  ispsf1 = (q1 < psfChisqCut1)
854  ispsf2 = (q2 < psfChisqCut2)
855 
856  pkres.psfFit1 = (chisq1, dof1)
857  pkres.psfFit2 = (chisq2, dof2)
858 
859  # check that the fit PSF spatial derivative terms aren't too big
860  if ispsf2:
861  fdx, fdy = X2[I_dx], X2[I_dy]
862  f0 = X2[I_psf]
863  # as a fraction of the PSF flux
864  dx = fdx/f0
865  dy = fdy/f0
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)))
869  if not ispsf2:
870  pkres.psfFitBigDecenter = True
871 
872  # Looks like a shifted PSF: try actually shifting the PSF by that amount
873  # and re-evaluate the fit.
874  if ispsf2:
875  psfimg2 = psf.computeImage(cx + dx, cy + dy)
876  # clip
877  pbb2 = psfimg2.getBBox()
878  pbb2.clip(fbb)
879  # clip image to bbox
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()
885 
886  # yuck! Update the PSF terms in the least-squares fit matrix.
887  Ab = A[:,:NT1]
888 
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]
897 
898  Aw = Ab * w[:,np.newaxis]
899  # re-solve...
900  Xb,rb,rankb,sb = np.linalg.lstsq(Aw, bw)
901  if len(rb) > 0:
902  chisqb = rb[0]
903  else:
904  chisqb = 1e30
905  dofb = sumr - len(Xb)
906  log.log(-9, 'rb, dofb %g %g' %(rb, dofb))
907  qb = chisqb / dofb
908  ispsf2 = (qb < psfChisqCut2b)
909  q2 = qb
910  X2 = Xb
911  log.logdebug('shifted PSF: new chisq/dof = %g; good? %s' %
912  (qb, ispsf2))
913  pkres.psfFit3 = (chisqb, dofb)
914 
915  # Which one do we keep?
916  if (((ispsf1 and ispsf2) and (q2 < q1)) or
917  (ispsf2 and not ispsf1)):
918  Xpsf = X2
919  chisq = chisq2
920  dof = dof2
921  log.log(-9, 'dof %g' % dof)
922  log.logdebug('Keeping shifted-PSF model')
923  cx += dx
924  cy += dy
925  pkres.psfFitWithDecenter = True
926  else:
927  # (arbitrarily set to X1 when neither fits well)
928  Xpsf = X1
929  chisq = chisq1
930  dof = dof1
931  log.log(-9, 'dof %g' % dof)
932  log.logdebug('Keeping unshifted PSF model')
933 
934  ispsf = (ispsf1 or ispsf2)
935 
936  # Save the PSF models in images for posterity.
937  if debugPsf:
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))
952  for ii in range(NP):
953  x,y = ipixes[ii,:]
954  psfmod.set(int(x),int(y), float(A[ii, I_psf] * Xpsf[I_psf]))
955  modelfp = afwDet.Footprint()
956  for (x,y) in ipixes:
957  modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
958  modelfp.normalize()
959 
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 # numpy array
966  pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb, True)
967  ww = np.zeros(valid.shape, np.float)
968  ww[valid] = w
969  pkres.psfFitDebugWeight = ww # numpy
970  pkres.psfFitDebugRampWeight = rw
971 
972 
973 
974  # Save things we learned about this peak for posterity...
975  pkres.psfFitR0 = R0
976  pkres.psfFitR1 = R1
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)
984 
985  if ispsf:
986  pkres.setDeblendedAsPsf()
987 
988  # replace the template image by the PSF + derivatives
989  # image.
990  log.logdebug('Deblending as PSF; setting template to PSF model')
991 
992  # Instantiate the PSF model and clip it to the footprint
993  psfimg = psf.computeImage(cx, cy)
994  # Scale by fit flux.
995  psfimg *= Xpsf[I_psf]
996  psfimg = psfimg.convertF()
997 
998  # Clip the Footprint to the PSF model image bbox.
999  fpcopy = afwDet.Footprint(fp)
1000  psfbb = psfimg.getBBox()
1001  fpcopy.clipTo(psfbb)
1002  bb = fpcopy.getBBox()
1003 
1004  # Copy the part of the PSF model within the clipped footprint.
1005  psfmod = afwImage.MaskedImageF(bb)
1006  afwDet.copyWithinFootprintImage(fpcopy, psfimg, psfmod.getImage())
1007  # Save it as our template.
1008  fpcopy.clipToNonzero(psfmod.getImage())
1009  fpcopy.normalize()
1010  pkres.setTemplate(psfmod, fpcopy)
1011 
1012  # DEBUG
1013  pkres.setPsfTemplate(psfmod, fpcopy)
1014 
An integer coordinate rectangle.
Definition: Box.h:53
double min
Definition: attributes.cc:216
double max
Definition: attributes.cc:218
A set of pixels in an Image.
Definition: Footprint.h:70
def lsst.meas.deblender.baseline._fitPsfs (   fp,
  peaks,
  fpres,
  log,
  psf,
  psffwhm,
  img,
  varimg,
  psfChisqCut1,
  psfChisqCut2,
  psfChisqCut2b,
  kwargs 
)
private
Fit a PSF + smooth background models to a small region around each
peak (plus other peaks that are nearby) in the given list of
peaks.

fp: Footprint containing the Peaks to model
peaks: a list of all the Peak objects within this Footprint
fpres: a PerFootprint result object where results will be placed

log: pex Log object
psf: afw PSF object
psffwhm: FWHM of the PSF, in pixels
img: umm, the Image in which this Footprint finds itself
varimg: Variance plane
psfChisqCut*: floats: cuts in chisq-per-pixel at which to accept
    the PSF model

Results go into the fpres.peaks objects.

Definition at line 527 of file baseline.py.

528  **kwargs):
529  '''
530  Fit a PSF + smooth background models to a small region around each
531  peak (plus other peaks that are nearby) in the given list of
532  peaks.
533 
534  fp: Footprint containing the Peaks to model
535  peaks: a list of all the Peak objects within this Footprint
536  fpres: a PerFootprint result object where results will be placed
537 
538  log: pex Log object
539  psf: afw PSF object
540  psffwhm: FWHM of the PSF, in pixels
541  img: umm, the Image in which this Footprint finds itself
542  varimg: Variance plane
543  psfChisqCut*: floats: cuts in chisq-per-pixel at which to accept
544  the PSF model
545 
546  Results go into the fpres.peaks objects.
547 
548  '''
549  fbb = fp.getBBox()
550  cpsf = CachingPsf(psf)
551 
552  # create mask image for pixels within the footprint
553  fmask = afwImage.MaskU(fbb)
554  fmask.setXY0(fbb.getMinX(), fbb.getMinY())
555  afwDet.setMaskFromFootprint(fmask, fp, 1)
556 
557  # pk.getF() -- retrieving the floating-point location of the peak
558  # -- actually shows up in the profile if we do it in the loop, so
559  # grab them all here.
560  peakF = [pk.getF() for pk in peaks]
561 
562  for pki,(pk,pkres,pkF) in enumerate(zip(peaks, fpres.peaks, peakF)):
563  log.logdebug('Peak %i' % pki)
564  _fitPsf(fp, fmask, pk, pkF, pkres, fbb, peaks, peakF, log, cpsf,
565  psffwhm, img, varimg,
566  psfChisqCut1, psfChisqCut2, psfChisqCut2b,
567  **kwargs)
568 
MaskT setMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
OR bitmask into all the Mask&#39;s pixels that are in the Footprint.
def lsst.meas.deblender.baseline._handle_flux_at_edge (   log,
  psffwhm,
  t1,
  tfoot,
  fp,
  maskedImage,
  x0,
  x1,
  y0,
  y1,
  psf,
  pk,
  sigma1,
  patchEdges 
)
private

Definition at line 1016 of file baseline.py.

1017  x0,x1,y0,y1, psf, pk, sigma1, patchEdges):
1018  # Import C++ routines
1019  import lsst.meas.deblender as deb
1020  butils = deb.BaselineUtilsF
1021 
1022  log.logdebug('Found significant flux at template edge.')
1023  # Compute the max of:
1024  # -symmetric-template-clipped image * PSF
1025  # -footprint-clipped image
1026  # Ie, extend the template by the PSF and "fill in" the
1027  # footprint.
1028  # Then find the symmetric template of that image.
1029 
1030  # The size we'll grow by
1031  S = psffwhm * 1.5
1032  # make it an odd integer
1033  S = (int(S + 0.5) / 2) * 2 + 1
1034 
1035  tbb = tfoot.getBBox()
1036  tbb.grow(S)
1037 
1038  # (footprint+margin)-clipped image;
1039  # we need the pixels OUTSIDE the footprint to be 0.
1040  fpcopy = afwDet.Footprint(fp)
1041  fpcopy.clipTo(tbb)
1042  padim = t1.Factory(tbb)
1043  afwDet.copyWithinFootprintMaskedImage(fpcopy, maskedImage, padim)
1044 
1045  # find pixels on the edge of the template
1046  edgepix = butils.getSignificantEdgePixels(t1.getImage(), tfoot, -1e6)
1047 
1048  # instantiate PSF image
1049  xc = int((x0 + x1)/2)
1050  yc = int((y0 + y1)/2)
1051  psfim = psf.computeImage(afwGeom.Point2D(xc, yc))
1052  pbb = psfim.getBBox()
1053  # shift PSF image to by centered on zero
1054  lx,ly = pbb.getMinX(), pbb.getMinY()
1055  psfim.setXY0(lx - xc, ly - yc)
1056  pbb = psfim.getBBox()
1057  # clip PSF to S, if necessary
1058  Sbox = afwGeom.Box2I(afwGeom.Point2I(-S, -S),
1059  afwGeom.Extent2I(2*S+1, 2*S+1))
1060  if not Sbox.contains(pbb):
1061  # clip PSF image
1062  psfim = psfim.Factory(psfim, Sbox, afwImage.PARENT, True)
1063  pbb = psfim.getBBox()
1064  px0 = pbb.getMinX()
1065  px1 = pbb.getMaxX()
1066  py0 = pbb.getMinY()
1067  py1 = pbb.getMaxY()
1068 
1069  # Compute the ramped-down edge pixels
1070  ramped = t1.getImage().Factory(tbb)
1071  Tout = ramped.getArray()
1072  Tin = t1.getImage().getArray()
1073  tx0,ty0 = t1.getX0(), t1.getY0()
1074  ox0,oy0 = ramped.getX0(), ramped.getY0()
1075  P = psfim.getArray()
1076  P /= P.max()
1077  # For each edge pixel, Tout = max(Tout, edgepix * PSF)
1078  for span in edgepix.getSpans():
1079  y = span.getY()
1080  for x in range(span.getX0(), span.getX1()+1):
1081  slc = (slice(y+py0 - oy0, y+py1+1 - oy0),
1082  slice(x+px0 - ox0, x+px1+1 - ox0))
1083  Tout[slc] = np.maximum(Tout[slc], Tin[y-ty0, x-tx0] * P)
1084 
1085  # Fill in the "padim" (which has the right variance and
1086  # mask planes) with the ramped pixels, outside the footprint
1087  I = (padim.getImage().getArray() == 0)
1088  padim.getImage().getArray()[I] = ramped.getArray()[I]
1089 
1090  fpcopy = afwDet.growFootprint(fpcopy, S)
1091  fpcopy.normalize()
1092 
1093  rtn,patched = butils.buildSymmetricTemplate(
1094  padim, fpcopy, pk, sigma1, True, patchEdges)
1095  # silly SWIG can't unpack pairs as tuples
1096  t2, tfoot2 = rtn[0], rtn[1]
1097  del rtn
1098 
1099  # This template footprint may extend outside the parent
1100  # footprint -- or the image. Clip it.
1101  # NOTE that this may make it asymmetric, unlike normal templates.
1102  imbb = maskedImage.getBBox()
1103  tfoot2.clipTo(imbb)
1104  tbb = tfoot2.getBBox()
1105  # clip template image to bbox
1106  t2 = t2.Factory(t2, tbb, afwImage.PARENT, True)
1107 
1108  return t2, tfoot2, patched
boost::shared_ptr< Footprint > growFootprint(Footprint const &foot, int nGrow, bool left, bool right, bool up, bool down)
Grow a Footprint in at least one of the cardinal directions, returning a new Footprint.
An integer coordinate rectangle.
Definition: Box.h:53
A set of pixels in an Image.
Definition: Footprint.h:70
def lsst.meas.deblender.baseline.deblend (   footprint,
  maskedImage,
  psf,
  psffwhm,
  psfChisqCut1 = 1.5,
  psfChisqCut2 = 1.5,
  psfChisqCut2b = 1.5,
  fitPsfs = True,
  medianSmoothTemplate = True,
  medianFilterHalfsize = 2,
  monotonicTemplate = True,
  weightTemplates = False,
  log = None,
  verbose = False,
  sigma1 = None,
  maxNumberOfPeaks = 0,
  findStrayFlux = True,
  assignStrayFlux = True,
  strayFluxToPointSources = 'necessary',
  strayFluxAssignment = 'r-to-peak',
  rampFluxAtEdge = False,
  patchEdges = False,
  tinyFootprintSize = 2,
  getTemplateSum = False,
  clipStrayFluxFraction = 0.001,
  clipFootprintToNonzero = True 
)
Deblend a single ``footprint`` in a ``maskedImage``.

Each ``Peak`` in the ``Footprint`` will produce a deblended child.

psfChisqCut1, psfChisqCut2: used when deciding whether a given
peak looks like a point source.  We build a small local model of
background + peak + neighboring peaks.  These are the
chi-squared-per-degree-of-freedom cuts (1=without and 2=with terms
allowing the peak position to move); larger values will result in
more peaks being classified as PSFs.

psfChisqCut2b: if the with-decenter fit is accepted, we then
apply the computed dx,dy to the source position and re-fit without
decentering.  The model is accepted if its chi-squared-per-DOF is
less than this value.

maxNumberOfPeaks: if positive, only deblend the brightest
        maxNumberOfPeaks peaks in the parent

Definition at line 228 of file baseline.py.

229  ):
230  '''
231  Deblend a single ``footprint`` in a ``maskedImage``.
232 
233  Each ``Peak`` in the ``Footprint`` will produce a deblended child.
234 
235  psfChisqCut1, psfChisqCut2: used when deciding whether a given
236  peak looks like a point source. We build a small local model of
237  background + peak + neighboring peaks. These are the
238  chi-squared-per-degree-of-freedom cuts (1=without and 2=with terms
239  allowing the peak position to move); larger values will result in
240  more peaks being classified as PSFs.
241 
242  psfChisqCut2b: if the with-decenter fit is accepted, we then
243  apply the computed dx,dy to the source position and re-fit without
244  decentering. The model is accepted if its chi-squared-per-DOF is
245  less than this value.
246 
247  maxNumberOfPeaks: if positive, only deblend the brightest
248  maxNumberOfPeaks peaks in the parent
249  '''
250  # Import C++ routines
251  import lsst.meas.deblender as deb
252  butils = deb.BaselineUtilsF
253 
254  validStrayPtSrc = ['never', 'necessary', 'always']
255  validStrayAssign = ['r-to-peak', 'r-to-footprint', 'nearest-footprint']
256  if not strayFluxToPointSources in validStrayPtSrc:
257  raise ValueError((('strayFluxToPointSources: value \"%s\" not in the '
258  + 'set of allowed values: ')
259  % strayFluxToPointSources) + str(validStrayPtSrc))
260  if not strayFluxAssignment in validStrayAssign:
261  raise ValueError((('strayFluxAssignment: value \"%s\" not in the '
262  + 'set of allowed values: ')
263  % strayFluxAssignment) + str(validStrayAssign))
264 
265  if log is None:
266  import lsst.pex.logging as pexLogging
267 
268  component = 'meas_deblender.baseline'
269  log = pexLogging.Log(pexLogging.Log.getDefaultLog(), component)
270 
271  if verbose: # pexLogging defines DEBUG in terms of "importance" == -verbosity
272  pexLogging.Trace.setVerbosity(component, -pexLogging.Log.DEBUG)
273 
274  img = maskedImage.getImage()
275  varimg = maskedImage.getVariance()
276  mask = maskedImage.getMask()
277 
278  fp = footprint
279  fp.normalize()
280  peaks = fp.getPeaks()
281  if maxNumberOfPeaks > 0:
282  peaks = peaks[:maxNumberOfPeaks]
283 
284  # Pull out the image bounds of the parent Footprint
285  imbb = img.getBBox()
286  bb = fp.getBBox()
287  if not imbb.contains(bb):
288  raise ValueError(('Footprint bounding-box %s extends outside image '
289  + 'bounding-box %s') % (str(bb), str(imbb)))
290  W,H = bb.getWidth(), bb.getHeight()
291  x0,y0 = bb.getMinX(), bb.getMinY()
292  x1,y1 = bb.getMaxX(), bb.getMaxY()
293 
294  # 'sigma1' is an estimate of the average noise level in this image.
295  if sigma1 is None:
296  # FIXME -- just within the bbox?
297  stats = afwMath.makeStatistics(varimg, mask, afwMath.MEDIAN)
298  sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
299  log.logdebug('Estimated sigma1 = %f' % sigma1)
300 
301  # Add the mask planes we will set.
302  for nm in ['SYMM_1SIG', 'SYMM_3SIG', 'MONOTONIC_1SIG']:
303  mask.addMaskPlane(nm)
304 
305  # get object that will hold our results
306  res = PerFootprint(fp, peaks=peaks)
307 
308  if fitPsfs:
309  # Find peaks that are well-fit by a PSF + background model.
310  _fitPsfs(fp, peaks, res, log, psf, psffwhm, img, varimg,
311  psfChisqCut1, psfChisqCut2, psfChisqCut2b,
312  tinyFootprintSize=tinyFootprintSize)
313 
314  # Create templates...
315  log.logdebug(('Creating templates for footprint at x0,y0,W,H = ' +
316  '(%i,%i, %i,%i)') % (x0,y0,W,H))
317  for peaki,pkres in enumerate(res.peaks):
318  log.logdebug('Deblending peak %i of %i' % (peaki, len(res.peaks)))
319  if pkres.skip or pkres.deblendedAsPsf:
320  continue
321  pk = pkres.peak
322  cx,cy = pk.getIx(), pk.getIy()
323  if not imbb.contains(afwGeom.Point2I(cx,cy)):
324  log.logdebug('Peak center is not inside image; skipping %i' %
325  pkres.pki)
326  pkres.setOutOfBounds()
327  continue
328  log.logdebug('computing template for peak %i at (%i,%i)' %
329  (pkres.pki, cx, cy))
330  S,patched = butils.buildSymmetricTemplate(
331  maskedImage, fp, pk, sigma1, True, patchEdges)
332  # SWIG doesn't know how to unpack a std::pair into a 2-tuple...
333  # (in this case, a nested pair)
334  t1, tfoot = S[0], S[1]
335  del S
336 
337  if t1 is None:
338  log.logdebug(('Peak %i at (%i,%i): failed to build symmetric ' +
339  'template') % (pkres.pki, cx,cy))
340  pkres.setFailedSymmetricTemplate()
341  continue
342 
343  if patched:
344  pkres.setPatched()
345 
346  # possibly save the original symmetric template
347  pkres.setOrigTemplate(t1, tfoot)
348 
349  if rampFluxAtEdge:
350  log.logdebug('Checking for significant flux at edge: sigma1=%g' % sigma1)
351  if (rampFluxAtEdge and
352  butils.hasSignificantFluxAtEdge(t1.getImage(), tfoot, 3*sigma1)):
353  log.logdebug("Template %i has significant flux at edge: ramping" % pkres.pki)
354  (t2, tfoot2, patched) = _handle_flux_at_edge(
355  log, psffwhm, t1, tfoot, fp, maskedImage, x0,x1,y0,y1,
356  psf, pk, sigma1, patchEdges)
357  pkres.setRampedTemplate(t2, tfoot2)
358  if patched:
359  pkres.setPatched()
360  t1 = t2
361  tfoot = tfoot2
362 
363  if medianSmoothTemplate:
364  filtsize = medianFilterHalfsize * 2 + 1
365  if t1.getWidth() >= filtsize and t1.getHeight() >= filtsize:
366  log.logdebug('Median filtering template %i' % pkres.pki)
367  # We want the output to go in "t1", so copy it into
368  # "inimg" for input
369  inimg = t1.Factory(t1, True)
370  butils.medianFilter(inimg, t1, medianFilterHalfsize)
371  # possible save this median-filtered template
372  pkres.setMedianFilteredTemplate(t1, tfoot)
373  else:
374  log.logdebug(('Not median-filtering template %i: size '
375  + '%i x %i smaller than required %i x %i') %
376  (pkres.pki, t1.getWidth(), t1.getHeight(),
377  filtsize, filtsize))
378 
379  if monotonicTemplate:
380  log.logdebug('Making template %i monotonic' % pkres.pki)
381  butils.makeMonotonic(t1, pk)
382 
383  if clipFootprintToNonzero:
384  tfoot.clipToNonzero(t1.getImage())
385  tfoot.normalize()
386 
387  pkres.setTemplate(t1, tfoot)
388 
389  # Prepare inputs to "apportionFlux" call.
390  # template maskedImages
391  tmimgs = []
392  # template footprints
393  tfoots = []
394  # deblended as psf
395  dpsf = []
396  # peak x,y
397  pkx = []
398  pky = []
399  # indices of valid templates
400  ibi = []
401 
402  for peaki,pkres in enumerate(res.peaks):
403  if pkres.skip:
404  continue
405  tmimgs.append(pkres.templateMaskedImage)
406  tfoots.append(pkres.templateFootprint)
407  # for stray flux...
408  dpsf.append(pkres.deblendedAsPsf)
409  pk = pkres.peak
410  pkx.append(pk.getIx())
411  pky.append(pk.getIy())
412  ibi.append(pkres.pki)
413 
414  if weightTemplates:
415  # Reweight the templates by doing a least-squares fit to the image
416  A = np.zeros((W * H, len(tmimgs)))
417  b = img.getArray()[y0:y1+1, x0:x1+1].ravel()
418 
419  for mim,i in zip(tmimgs, ibi):
420  ibb = mim.getBBox()
421  ix0,iy0 = ibb.getMinX(), ibb.getMinY()
422  pkres = res.peaks[i]
423  foot = pkres.templateFootprint
424  ima = mim.getImage().getArray()
425  for sp in foot.getSpans():
426  sy, sx0, sx1 = sp.getY(), sp.getX0(), sp.getX1()
427  imrow = ima[sy-iy0, sx0-ix0 : 1+sx1-ix0]
428  r0 = (sy-y0)*W
429  A[r0 + sx0-x0: r0+1+sx1-x0, i] = imrow
430 
431  X1,r1,rank1,s1 = np.linalg.lstsq(A, b)
432  del A
433  del b
434 
435  for mim,i,w in zip(tmimgs, ibi, X1):
436  im = mim.getImage()
437  im *= w
438  res.peaks[i].setTemplateWeight(w)
439 
440  # FIXME -- Remove templates that are too similar (via dot-product
441  # test)?
442 
443  # Now apportion flux according to the templates
444  log.logdebug('Apportioning flux among %i templates' % len(tmimgs))
445  sumimg = afwImage.ImageF(bb)
446  #.getDimensions())
447  #sumimg.setXY0(bb.getMinX(), bb.getMinY())
448 
449  strayflux = afwDet.HeavyFootprintPtrListF()
450 
451  strayopts = 0
452  if findStrayFlux:
453  strayopts |= butils.ASSIGN_STRAYFLUX
454  if strayFluxToPointSources == 'necessary':
455  strayopts |= butils.STRAYFLUX_TO_POINT_SOURCES_WHEN_NECESSARY
456  elif strayFluxToPointSources == 'always':
457  strayopts |= butils.STRAYFLUX_TO_POINT_SOURCES_ALWAYS
458 
459  if strayFluxAssignment == 'r-to-peak':
460  # this is the default
461  pass
462  elif strayFluxAssignment == 'r-to-footprint':
463  strayopts |= butils.STRAYFLUX_R_TO_FOOTPRINT
464  elif strayFluxAssignment == 'nearest-footprint':
465  strayopts |= butils.STRAYFLUX_NEAREST_FOOTPRINT
466 
467  portions = butils.apportionFlux(maskedImage, fp, tmimgs, tfoots, sumimg,
468  dpsf, pkx, pky, strayflux, strayopts,
469  clipStrayFluxFraction)
470  if getTemplateSum:
471  res.setTemplateSum(sumimg)
472 
473  # Save the apportioned fluxes
474  ii = 0
475  for j, (pk, pkres) in enumerate(zip(peaks, res.peaks)):
476  if pkres.skip:
477  continue
478  pkres.setFluxPortion(portions[ii])
479  ii += 1
480 
481  if findStrayFlux:
482  # NOTE that due to a swig bug
483  # (https://github.com/swig/swig/issues/59)
484  # we CANNOT iterate over "strayflux", but must index into it.
485  stray = strayflux[j]
486  else:
487  stray = None
488 
489  pkres.setStrayFlux(stray)
490 
491  # Set child footprints to contain the right number of peaks.
492  for j, (pk, pkres) in enumerate(zip(peaks, res.peaks)):
493  if pkres.skip:
494  continue
495 
496  for foot,add in [(pkres.templateFootprint, True),
497  (pkres.origFootprint, True),
498  (pkres.strayFlux, False)]:
499  if foot is None:
500  continue
501  pks = foot.getPeaks()
502  pks.clear()
503  if add:
504  pks.push_back(pk)
505 
506  return res
a place to record messages and descriptions of the state of processing.
Definition: Log.h:154
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1023