LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
baseline.py
Go to the documentation of this file.
1 from builtins import zip
2 from builtins import str
3 from builtins import range
4 from builtins import object
5 #!/usr/bin/env python
6 #
7 # LSST Data Management System
8 #
9 # Copyright 2008-2015 AURA/LSST.
10 #
11 # This product includes software developed by the
12 # LSST Project (http://www.lsst.org/).
13 #
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the LSST License Statement and
25 # the GNU General Public License along with this program. If not,
26 # see <https://www.lsstcorp.org/LegalNotices/>.
27 #
28 import math
29 import numpy as np
30 
32 import lsst.afw.image as afwImage
33 import lsst.afw.detection as afwDet
34 import lsst.afw.geom as afwGeom
35 import lsst.afw.math as afwMath
36 
37 # Result objects; will probably go away as we push more results
38 # into earlier-created Source objects, but useful for now for
39 # hauling debugging results around.
40 
41 
42 class PerFootprint(object):
43  """Result of deblending a single parent Footprint."""
44 
45  def __init__(self, fp, peaks=None):
46  """!
47  Creates a result object for the given parent Footprint 'fp'.
48 
49  @param[in] fp Footprint
50  @param[in,out] peaks If specified, the list of peaks to use; default is
51  fp.getPeaks().
52  """
53  # PerPeak objects, one per Peak
54  self.peaks = []
55  if peaks is None:
56  peaks = fp.getPeaks()
57  for pki, pk in enumerate(peaks):
58  pkres = PerPeak()
59  pkres.peak = pk
60  pkres.pki = pki
61  self.peaks.append(pkres)
62 
63  self.templateSum = None
64 
65  def setTemplateSum(self, tsum):
66  self.templateSum = tsum
67 
68 
69 class PerPeak(object):
70  """!
71  Result of deblending a single Peak within a parent Footprint.
72 
73  There is one of these objects for each Peak in the Footprint.
74  """
75 
76  def __init__(self):
77  # Peak object
78  self.peak = None
79  # int, peak index number
80  self.pki = None
81  # union of all the ways of failing...
82  self.skip = False
83 
84  self.outOfBounds = False
85  self.tinyFootprint = False
86  self.noValidPixels = False
87  self.deblendedAsPsf = False
88 
89  # Field set during _fitPsf:
90  self.psfFitFailed = False
91  self.psfFitBadDof = False
92  # (chisq, dof) for PSF fit without decenter
93  self.psfFit1 = None
94  # (chisq, dof) for PSF fit with decenter
95  self.psfFit2 = None
96  # (chisq, dof) for PSF fit after applying decenter
97  self.psfFit3 = None
98  # decentered PSF fit wanted to move the center too much
99  self.psfFitBigDecenter = False
100  # was the fit with decenter better?
101  self.psfFitWithDecenter = False
102  #
103  self.psfFitR0 = None
104  self.psfFitR1 = None
105  self.psfFitStampExtent = None
106  self.psfFitCenter = None
107  self.psfFitBest = None
108  self.psfFitParams = None
109  self.psfFitFlux = None
110  self.psfFitNOthers = None
111 
112  # Things only set in _fitPsf when debugging is turned on:
113  self.psfFitDebugPsf0Img = None
114  self.psfFitDebugPsfImg = None
117 
119 
120  # The actual template Image and Footprint
121  self.templateImage = None
122  self.templateFootprint = None
123 
124  # The flux assigned to this template -- a MaskedImage
125  self.fluxPortion = None
126 
127  # The stray flux assigned to this template (may be None), a HeavyFootprint
128  self.strayFlux = None
129 
130  self.hasRampedTemplate = False
131 
132  self.patched = False
133 
134  # debug -- a copy of the original symmetric template
135  self.origTemplate = None
136  self.origFootprint = None
137  # MaskedImage
138  self.rampedTemplate = None
139  # MaskedImage
141 
142  # when least-squares fitting templates, the template weight.
143  self.templateWeight = 1.0
144 
145  def __str__(self):
146  return (('Per-peak deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
147  (self.outOfBounds, self.deblendedAsPsf))
148 
149  @property
150  def psfFitChisq(self):
151  chisq, dof = self.psfFitBest
152  return chisq
153 
154  @property
155  def psfFitDof(self):
156  chisq, dof = self.psfFitBest
157  return dof
158 
159  def getFluxPortion(self, strayFlux=True):
160  """!
161  Return a HeavyFootprint containing the flux apportioned to this peak.
162 
163  @param[in] strayFlux include stray flux also?
164  """
165  if self.templateFootprint is None or self.fluxPortion is None:
166  return None
168  self.fluxPortion)
169  if strayFlux:
170  if self.strayFlux is not None:
171  heavy.normalize()
172  self.strayFlux.normalize()
173  heavy = afwDet.mergeHeavyFootprintsF(heavy, self.strayFlux)
174 
175  return heavy
176 
177  def setStrayFlux(self, stray):
178  self.strayFlux = stray
179 
180  def setFluxPortion(self, mimg):
181  self.fluxPortion = mimg
182 
183  def setTemplateWeight(self, w):
184  self.templateWeight = w
185 
186  def setPatched(self):
187  self.patched = True
188 
189  # DEBUG
190  def setOrigTemplate(self, t, tfoot):
191  self.origTemplate = t.Factory(t, True)
192  self.origFootprint = tfoot
193 
194  def setRampedTemplate(self, t, tfoot):
195  self.hasRampedTemplate = True
196  self.rampedTemplate = t.Factory(t, True)
197 
198  def setMedianFilteredTemplate(self, t, tfoot):
199  self.medianFilteredTemplate = t.Factory(t, True)
200 
201  def setPsfTemplate(self, tim, tfoot):
203  self.psfTemplate = tim.Factory(tim, True)
204 
205  def setOutOfBounds(self):
206  self.outOfBounds = True
207  self.skip = True
208 
209  def setTinyFootprint(self):
210  self.tinyFootprint = True
211  self.skip = True
212 
213  def setNoValidPixels(self):
214  self.noValidPixels = True
215  self.skip = True
216 
217  def setPsfFitFailed(self):
218  self.psfFitFailed = True
219 
220  def setBadPsfDof(self):
221  self.psfFitBadDof = True
222 
223  def setDeblendedAsPsf(self):
224  self.deblendedAsPsf = True
225 
227  self.failedSymmetricTemplate = True
228  self.skip = True
229 
230  def setTemplate(self, image, footprint):
231  self.templateImage = image
232  self.templateFootprint = footprint
233 
234 
235 def deblend(footprint, maskedImage, psf, psffwhm,
236  psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=True,
237  medianSmoothTemplate=True, medianFilterHalfsize=2,
238  monotonicTemplate=True, weightTemplates=False,
239  log=None, verbose=False, sigma1=None, maxNumberOfPeaks=0,
240  findStrayFlux=True, assignStrayFlux=True,
241  strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak',
242  rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2,
243  getTemplateSum=False,
244  clipStrayFluxFraction=0.001, clipFootprintToNonzero=True,
245  ):
246  """!
247  Deblend a single ``footprint`` in a ``maskedImage``.
248 
249  Each ``Peak`` in the ``Footprint`` will produce a deblended child.
250 
251  psfChisqCut1, psfChisqCut2: used when deciding whether a given
252  peak looks like a point source. We build a small local model of
253  background + peak + neighboring peaks. These are the
254  chi-squared-per-degree-of-freedom cuts (1=without and 2=with terms
255  allowing the peak position to move); larger values will result in
256  more peaks being classified as PSFs.
257 
258  psfChisqCut2b: if the with-decenter fit is accepted, we then
259  apply the computed dx,dy to the source position and re-fit without
260  decentering. The model is accepted if its chi-squared-per-DOF is
261  less than this value.
262 
263  maxNumberOfPeaks: if positive, only deblend the brightest
264  maxNumberOfPeaks peaks in the parent
265  """
266  # Import C++ routines
267  import lsst.meas.deblender as deb
268  butils = deb.BaselineUtilsF
269 
270  validStrayPtSrc = ['never', 'necessary', 'always']
271  validStrayAssign = ['r-to-peak', 'r-to-footprint', 'nearest-footprint', 'trim']
272  if not strayFluxToPointSources in validStrayPtSrc:
273  raise ValueError((('strayFluxToPointSources: value \"%s\" not in the '
274  'set of allowed values: ')
275  % strayFluxToPointSources) + str(validStrayPtSrc))
276  if not strayFluxAssignment in validStrayAssign:
277  raise ValueError((('strayFluxAssignment: value \"%s\" not in the '
278  'set of allowed values: ')
279  % strayFluxAssignment) + str(validStrayAssign))
280 
281  if log is None:
282  import lsst.log as lsstLog
283 
284  component = 'meas_deblender.baseline'
285  log = lsstLog.Log.getLogger(component)
286 
287  if verbose:
288  log.setLevel(lsstLog.Log.TRACE)
289 
290  img = maskedImage.getImage()
291  varimg = maskedImage.getVariance()
292  mask = maskedImage.getMask()
293 
294  fp = footprint
295  fp.normalize()
296  peaks = fp.getPeaks()
297  if maxNumberOfPeaks > 0:
298  peaks = peaks[:maxNumberOfPeaks]
299 
300  # Pull out the image bounds of the parent Footprint
301  imbb = img.getBBox()
302  bb = fp.getBBox()
303  if not imbb.contains(bb):
304  raise ValueError(('Footprint bounding-box %s extends outside image '
305  + 'bounding-box %s') % (str(bb), str(imbb)))
306  W, H = bb.getWidth(), bb.getHeight()
307  x0, y0 = bb.getMinX(), bb.getMinY()
308  x1, y1 = bb.getMaxX(), bb.getMaxY()
309 
310  # 'sigma1' is an estimate of the average noise level in this image.
311  if sigma1 is None:
312  # FIXME -- just within the bbox?
313  stats = afwMath.makeStatistics(varimg, mask, afwMath.MEDIAN)
314  sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
315  log.trace('Estimated sigma1 = %f', sigma1)
316 
317  # get object that will hold our results
318  res = PerFootprint(fp, peaks=peaks)
319 
320  if fitPsfs:
321  # Find peaks that are well-fit by a PSF + background model.
322  _fitPsfs(fp, peaks, res, log, psf, psffwhm, img, varimg,
323  psfChisqCut1, psfChisqCut2, psfChisqCut2b,
324  tinyFootprintSize=tinyFootprintSize)
325 
326  # Create templates...
327  log.trace('Creating templates for footprint at x0,y0,W,H = ' +
328  '(%i, %i, %i, %i)', x0, y0, W, H)
329  for peaki, pkres in enumerate(res.peaks):
330  log.trace('Deblending peak %i of %i', peaki, len(res.peaks))
331  if pkres.skip or pkres.deblendedAsPsf:
332  continue
333  pk = pkres.peak
334  cx, cy = pk.getIx(), pk.getIy()
335  if not imbb.contains(afwGeom.Point2I(cx,cy)):
336  log.trace('Peak center is not inside image; skipping %i', pkres.pki)
337  pkres.setOutOfBounds()
338  continue
339  log.trace('computing template for peak %i at (%i, %i)', pkres.pki, cx, cy)
340  S, patched = butils.buildSymmetricTemplate(maskedImage, fp, pk, sigma1, True, patchEdges)
341  # SWIG doesn't know how to unpack a std::pair into a 2-tuple...
342  # (in this case, a nested pair)
343  t1, tfoot = S[0], S[1]
344  del S
345 
346  if t1 is None:
347  log.trace('Peak %i at (%i, %i): failed to build symmetric ' +
348  'template', pkres.pki, cx, cy)
349  pkres.setFailedSymmetricTemplate()
350  continue
351 
352  if patched:
353  pkres.setPatched()
354 
355  # possibly save the original symmetric template
356  pkres.setOrigTemplate(t1, tfoot)
357 
358  if rampFluxAtEdge:
359  log.trace('Checking for significant flux at edge: sigma1=%g', sigma1)
360  if (rampFluxAtEdge and butils.hasSignificantFluxAtEdge(t1, tfoot, 3*sigma1)):
361  log.trace("Template %i has significant flux at edge: ramping", pkres.pki)
362  try:
363  (t2, tfoot2, patched) = _handle_flux_at_edge(log, psffwhm, t1, tfoot, fp,
364  maskedImage, x0, x1, y0, y1,
365  psf, pk, sigma1, patchEdges)
366  except lsst.pex.exceptions.Exception as exc:
367  if (isinstance(exc, lsst.pex.exceptions.InvalidParameterError) and "CoaddPsf" in str(exc)):
368  pkres.setOutOfBounds()
369  continue
370  raise
371  pkres.setRampedTemplate(t2, tfoot2)
372  if patched:
373  pkres.setPatched()
374  t1 = t2
375  tfoot = tfoot2
376 
377  if medianSmoothTemplate:
378  filtsize = medianFilterHalfsize*2 + 1
379  if t1.getWidth() >= filtsize and t1.getHeight() >= filtsize:
380  log.trace('Median filtering template %i', pkres.pki)
381  # We want the output to go in "t1", so copy it into
382  # "inimg" for input
383  inimg = t1.Factory(t1, True)
384  butils.medianFilter(inimg, t1, medianFilterHalfsize)
385  # possible save this median-filtered template
386  pkres.setMedianFilteredTemplate(t1, tfoot)
387  else:
388  log.trace('Not median-filtering template %i: size ' +
389  '%i x %i smaller than required %i x %i',
390  pkres.pki, t1.getWidth(), t1.getHeight(), filtsize, filtsize)
391  if monotonicTemplate:
392  log.trace('Making template %i monotonic', pkres.pki)
393  butils.makeMonotonic(t1, pk)
394 
395  if clipFootprintToNonzero:
396  tfoot.clipToNonzero(t1)
397  tfoot.normalize()
398  if not tfoot.getBBox().isEmpty() and tfoot.getBBox() != t1.getBBox(afwImage.PARENT):
399  t1 = t1.Factory(t1, tfoot.getBBox(), afwImage.PARENT, True)
400 
401  pkres.setTemplate(t1, tfoot)
402 
403  # Prepare inputs to "apportionFlux" call.
404  # template maskedImages
405  tmimgs = []
406  # template footprints
407  tfoots = []
408  # deblended as psf
409  dpsf = []
410  # peak x,y
411  pkx = []
412  pky = []
413  # indices of valid templates
414  ibi = []
415 
416  for peaki, pkres in enumerate(res.peaks):
417  if pkres.skip:
418  continue
419  tmimgs.append(pkres.templateImage)
420  tfoots.append(pkres.templateFootprint)
421  # for stray flux...
422  dpsf.append(pkres.deblendedAsPsf)
423  pk = pkres.peak
424  pkx.append(pk.getIx())
425  pky.append(pk.getIy())
426  ibi.append(pkres.pki)
427 
428  if weightTemplates:
429  # Reweight the templates by doing a least-squares fit to the image
430  A = np.zeros((W*H, len(tmimgs)))
431  b = img.getArray()[y0:y1+1, x0:x1+1].ravel()
432 
433  for mim, i in zip(tmimgs, ibi):
434  ibb = mim.getBBox()
435  ix0, iy0 = ibb.getMinX(), ibb.getMinY()
436  pkres = res.peaks[i]
437  foot = pkres.templateFootprint
438  ima = mim.getImage().getArray()
439  for sp in foot.getSpans():
440  sy, sx0, sx1 = sp.getY(), sp.getX0(), sp.getX1()
441  imrow = ima[sy-iy0, sx0-ix0: 1+sx1-ix0]
442  r0 = (sy-y0)*W
443  A[r0 + sx0-x0: r0+1+sx1-x0, i] = imrow
444 
445  X1, r1, rank1, s1 = np.linalg.lstsq(A, b)
446  del A
447  del b
448 
449  for mim, i, w in zip(tmimgs, ibi, X1):
450  im = mim.getImage()
451  im *= w
452  res.peaks[i].setTemplateWeight(w)
453 
454  # FIXME -- Remove templates that are too similar (via dot-product test)?
455 
456  # Now apportion flux according to the templates
457  log.trace('Apportioning flux among %i templates', len(tmimgs))
458  sumimg = afwImage.ImageF(bb)
459  #.getDimensions())
460  #sumimg.setXY0(bb.getMinX(), bb.getMinY())
461 
462  strayflux = afwDet.HeavyFootprintPtrListF()
463 
464  strayopts = 0
465  if strayFluxAssignment == 'trim':
466  findStrayFlux = False
467  strayopts |= butils.STRAYFLUX_TRIM
468  if findStrayFlux:
469  strayopts |= butils.ASSIGN_STRAYFLUX
470  if strayFluxToPointSources == 'necessary':
471  strayopts |= butils.STRAYFLUX_TO_POINT_SOURCES_WHEN_NECESSARY
472  elif strayFluxToPointSources == 'always':
473  strayopts |= butils.STRAYFLUX_TO_POINT_SOURCES_ALWAYS
474 
475  if strayFluxAssignment == 'r-to-peak':
476  # this is the default
477  pass
478  elif strayFluxAssignment == 'r-to-footprint':
479  strayopts |= butils.STRAYFLUX_R_TO_FOOTPRINT
480  elif strayFluxAssignment == 'nearest-footprint':
481  strayopts |= butils.STRAYFLUX_NEAREST_FOOTPRINT
482 
483  portions = butils.apportionFlux(maskedImage, fp, tmimgs, tfoots, sumimg, dpsf,
484  pkx, pky, strayflux, strayopts, clipStrayFluxFraction)
485 
486  # Shrink parent to union of children
487  if strayFluxAssignment == 'trim':
488  fp.include(tfoots, True)
489 
490  if getTemplateSum:
491  res.setTemplateSum(sumimg)
492 
493  # Save the apportioned fluxes
494  ii = 0
495  for j, (pk, pkres) in enumerate(zip(peaks, res.peaks)):
496  if pkres.skip:
497  continue
498  pkres.setFluxPortion(portions[ii])
499 
500  if findStrayFlux:
501  # NOTE that due to a swig bug (https://github.com/swig/swig/issues/59)
502  # we CANNOT iterate over "strayflux", but must index into it.
503  stray = strayflux[ii]
504  else:
505  stray = None
506  ii += 1
507 
508  pkres.setStrayFlux(stray)
509 
510  # Set child footprints to contain the right number of peaks.
511  for j, (pk, pkres) in enumerate(zip(peaks, res.peaks)):
512  if pkres.skip:
513  continue
514 
515  for foot, add in [(pkres.templateFootprint, True), (pkres.origFootprint, True),
516  (pkres.strayFlux, False)]:
517  if foot is None:
518  continue
519  pks = foot.getPeaks()
520  pks.clear()
521  if add:
522  pks.append(pk)
523 
524  return res
525 
526 
527 class CachingPsf(object):
528  """!
529  In the PSF fitting code, we request PSF models for all peaks near
530  the one being fit. This was turning out to be quite expensive in
531  some cases. Here, we cache the PSF models to bring the cost down
532  closer to O(N) rather than O(N^2).
533  """
534 
535  def __init__(self, psf):
536  self.cache = {}
537  self.psf = psf
538 
539  def computeImage(self, cx, cy):
540  im = self.cache.get((cx, cy), None)
541  if im is not None:
542  return im
543  try:
544  im = self.psf.computeImage(afwGeom.Point2D(cx, cy))
546  im = self.psf.computeImage()
547  self.cache[(cx, cy)] = im
548  return im
549 
550 
551 def _fitPsfs(fp, peaks, fpres, log, psf, psffwhm, img, varimg,
552  psfChisqCut1, psfChisqCut2, psfChisqCut2b,
553  **kwargs):
554  """!
555  Fit a PSF + smooth background models to a small region around each
556  peak (plus other peaks that are nearby) in the given list of
557  peaks.
558 
559  @param[in] fp Footprint containing the Peaks to model
560  @param[in] peaks a list of all the Peak objects within this Footprint
561  @param[in,out] fpres a PerFootprint result object where results will be placed
562  @param[in,out] log pex Log object
563  @param[in] psf afw PSF object
564  @param[in] psffwhm FWHM of the PSF, in pixels
565  @param[in] img the Image in which this Footprint finds itself
566  @param[in] varimg Variance plane
567  @param[in] psfChisqCut* floats: cuts in chisq-per-pixel at which to accept
568  the PSF model
569 
570  Results go into the fpres.peaks objects.
571  """
572  fbb = fp.getBBox()
573  cpsf = CachingPsf(psf)
574 
575  # create mask image for pixels within the footprint
576  fmask = afwImage.MaskU(fbb)
577  fmask.setXY0(fbb.getMinX(), fbb.getMinY())
578  afwDet.setMaskFromFootprint(fmask, fp, 1)
579 
580  # pk.getF() -- retrieving the floating-point location of the peak
581  # -- actually shows up in the profile if we do it in the loop, so
582  # grab them all here.
583  peakF = [pk.getF() for pk in peaks]
584 
585  for pki,(pk,pkres,pkF) in enumerate(zip(peaks, fpres.peaks, peakF)):
586  log.trace('Peak %i', pki)
587  _fitPsf(fp, fmask, pk, pkF, pkres, fbb, peaks, peakF, log, cpsf, psffwhm,
588  img, varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b,
589  **kwargs)
590 
591 
592 def _fitPsf(fp, fmask, pk, pkF, pkres, fbb, peaks, peaksF, log, psf, psffwhm,
593  img, varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b,
594  tinyFootprintSize=2,
595  ):
596  """!
597  Fit a PSF + smooth background model (linear) to a small region around the peak.
598 
599  @param[in] fp Footprint
600  @param[in] fmask the Mask plane for pixels in the Footprint
601  @param[in] pk the Peak within the Footprint that we are going to fit a PSF model for
602  @param[in] pkF the floating-point coordinates of this peak
603  @param[in,out] pkres a PerPeak object that will hold the results for this peak
604  @param[in] fbb the bounding-box of this Footprint (a Box2I)
605  @param[in] peaks a list of all the Peak objects within this Footprint
606  @param[in] peaksF a python list of the floating-point (x,y) peak locations
607  @param[in,out] log pex Log object
608  @param[in] psf afw PSF object
609  @param[in] psffwhm FWHM of the PSF, in pixels
610  @param[in] img the Image in which this Footprint finds itself
611  @param[in] varimg Variance plane
612  @param[in] psfChisqCut* floats: cuts in chisq-per-pixel at which to accept the PSF model
613  """
614  import lsstDebug
615  # my __name__ is lsst.meas.deblender.baseline
616  debugPlots = lsstDebug.Info(__name__).plots
617  debugPsf = lsstDebug.Info(__name__).psf
618 
619  # The small region is a disk out to R0, plus a ramp with
620  # decreasing weight down to R1.
621  R0 = int(math.ceil(psffwhm*1.))
622  # ramp down to zero weight at this radius...
623  R1 = int(math.ceil(psffwhm*1.5))
624  cx, cy = pkF.getX(), pkF.getY()
625  psfimg = psf.computeImage(cx, cy)
626  # R2: distance to neighbouring peak in order to put it into the model
627  R2 = R1 + min(psfimg.getWidth(), psfimg.getHeight())/2.
628 
629  pbb = psfimg.getBBox()
630  pbb.clip(fbb)
631  px0, py0 = psfimg.getX0(), psfimg.getY0()
632 
633  # Make sure we haven't been given a substitute PSF that's nowhere near where we want, as may occur if
634  # "Cannot compute CoaddPsf at point (xx,yy); no input images at that point."
635  if not pbb.contains(afwGeom.Point2I(int(cx), int(cy))):
636  pkres.setOutOfBounds()
637  return
638 
639  # The bounding-box of the local region we are going to fit ("stamp")
640  xlo = int(math.floor(cx - R1))
641  ylo = int(math.floor(cy - R1))
642  xhi = int(math.ceil(cx + R1))
643  yhi = int(math.ceil(cy + R1))
644  stampbb = afwGeom.Box2I(afwGeom.Point2I(xlo, ylo), afwGeom.Point2I(xhi, yhi))
645  stampbb.clip(fbb)
646  xlo, xhi = stampbb.getMinX(), stampbb.getMaxX()
647  ylo, yhi = stampbb.getMinY(), stampbb.getMaxY()
648  if xlo > xhi or ylo > yhi:
649  log.trace('Skipping this peak: out of bounds')
650  pkres.setOutOfBounds()
651  return
652 
653  # drop tiny footprints too?
654  if min(stampbb.getWidth(), stampbb.getHeight()) <= max(tinyFootprintSize, 2):
655  # Minimum size limit of 2 comes from the "PSF dx" calculation, which involves shifting the PSF
656  # by one pixel to the left and right.
657  log.trace('Skipping this peak: tiny footprint / close to edge')
658  pkres.setTinyFootprint()
659  return
660 
661  # find other peaks within range...
662  otherpeaks = []
663  for pk2, pkF2 in zip(peaks, peaksF):
664  if pk2 == pk:
665  continue
666  if pkF.distanceSquared(pkF2) > R2**2:
667  continue
668  opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
669  if not opsfimg.getBBox(afwImage.LOCAL).overlaps(stampbb):
670  continue
671  otherpeaks.append(opsfimg)
672  log.trace('%i other peaks within range', len(otherpeaks))
673 
674  # Now we are going to do a least-squares fit for the flux in this
675  # PSF, plus a decenter term, a linear sky, and fluxes of nearby
676  # sources (assumed point sources). Build up the matrix...
677  # Number of terms -- PSF flux, constant sky, X, Y, + other PSF fluxes
678  NT1 = 4 + len(otherpeaks)
679  # + PSF dx, dy
680  NT2 = NT1 + 2
681  # Number of pixels -- at most
682  NP = (1 + yhi - ylo)*(1 + xhi - xlo)
683  # indices of columns in the "A" matrix.
684  I_psf = 0
685  I_sky = 1
686  I_sky_ramp_x = 2
687  I_sky_ramp_y = 3
688  # offset of other psf fluxes:
689  I_opsf = 4
690  I_dx = NT1 + 0
691  I_dy = NT1 + 1
692 
693  # Build the matrix "A", rhs "b" and weight "w".
694  ix0, iy0 = img.getX0(), img.getY0()
695  fx0, fy0 = fbb.getMinX(), fbb.getMinY()
696  fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
697  islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
698  fmask_sub = fmask .getArray()[fslice]
699  var_sub = varimg.getArray()[islice]
700  img_sub = img .getArray()[islice]
701 
702  # Clip the PSF image to match its bbox
703  psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
704  pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
705  px0, px1 = pbb.getMinX(), pbb.getMaxX()
706  py0, py1 = pbb.getMinY(), pbb.getMaxY()
707 
708  # Compute the "valid" pixels within our region-of-interest
709  valid = (fmask_sub > 0)
710  xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
711  RR = ((xx - cx)**2)[np.newaxis, :] + ((yy - cy)**2)[:, np.newaxis]
712  valid *= (RR <= R1**2)
713  valid *= (var_sub > 0)
714  NP = valid.sum()
715 
716  if NP == 0:
717  log.warn('Skipping peak at (%.1f, %.1f): no unmasked pixels nearby' % (cx, cy))
718  pkres.setNoValidPixels()
719  return
720 
721  # pixel coords of valid pixels
722  XX, YY = np.meshgrid(xx, yy)
723  ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
724 
725  inpsfx = (xx >= px0)*(xx <= px1)
726  inpsfy = (yy >= py0)*(yy <= py1)
727  inpsf = np.outer(inpsfy, inpsfx)
728  indx = np.outer(inpsfy, (xx > px0)*(xx < px1))
729  indy = np.outer((yy > py0)*(yy < py1), inpsfx)
730 
731  del inpsfx
732  del inpsfy
733 
734  def _overlap(xlo, xhi, xmin, xmax):
735  assert((xlo <= xmax) and (xhi >= xmin) and
736  (xlo <= xhi) and (xmin <= xmax))
737  xloclamp = max(xlo, xmin)
738  Xlo = xloclamp - xlo
739  xhiclamp = min(xhi, xmax)
740  Xhi = Xlo + (xhiclamp - xloclamp)
741  assert(xloclamp >= 0)
742  assert(Xlo >= 0)
743  return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
744 
745  A = np.zeros((NP, NT2))
746  # Constant term
747  A[:, I_sky] = 1.
748  # Sky slope terms: dx, dy
749  A[:, I_sky_ramp_x] = ipixes[:, 0] + (xlo-cx)
750  A[:, I_sky_ramp_y] = ipixes[:, 1] + (ylo-cy)
751 
752  # whew, grab the valid overlapping PSF pixels
753  px0, px1 = pbb.getMinX(), pbb.getMaxX()
754  py0, py1 = pbb.getMinY(), pbb.getMaxY()
755  sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
756  sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
757  dpx0, dpy0 = px0 - xlo, py0 - ylo
758  psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
759  psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
760  psfsub = psfarr[psf_y_slice, psf_x_slice]
761  vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
762  A[inpsf[valid], I_psf] = psfsub[vsub]
763 
764  # PSF dx -- by taking the half-difference of shifted-by-one and
765  # shifted-by-minus-one.
766  oldsx = (sx1, sx2, sx3, sx4)
767  sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0+1, px1-1)
768  psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1] -
769  psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1])/2.
770  vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
771  A[indx[valid], I_dx] = psfsub[vsub]
772  # revert x indices...
773  (sx1, sx2, sx3, sx4) = oldsx
774 
775  # PSF dy
776  sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0+1, py1-1)
777  psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice] -
778  psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice])/2.
779  vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
780  A[indy[valid], I_dy] = psfsub[vsub]
781 
782  # other PSFs...
783  for j, opsf in enumerate(otherpeaks):
784  obb = opsf.getBBox()
785  ino = np.outer((yy >= obb.getMinY())*(yy <= obb.getMaxY()),
786  (xx >= obb.getMinX())*(xx <= obb.getMaxX()))
787  dpx0, dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
788  sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
789  sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
790  opsfarr = opsf.getArray()
791  psfsub = opsfarr[sy3 - dpy0: sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
792  vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
793  A[ino[valid], I_opsf + j] = psfsub[vsub]
794 
795  b = img_sub[valid]
796 
797  # Weights -- from ramp and image variance map.
798  # Ramp weights -- from 1 at R0 down to 0 at R1.
799  rw = np.ones_like(RR)
800  ii = (RR > R0**2)
801  rr = np.sqrt(RR[ii])
802  rw[ii] = np.maximum(0, 1. - ((rr - R0)/(R1 - R0)))
803  w = np.sqrt(rw[valid]/var_sub[valid])
804  # save the effective number of pixels
805  sumr = np.sum(rw[valid])
806  log.debug('sumr = %g' % sumr)
807 
808  del ii
809 
810  Aw = A*w[:, np.newaxis]
811  bw = b*w
812 
813  if debugPlots:
814  import pylab as plt
815  plt.clf()
816  N = NT2 + 2
817  R, C = 2, (N+1)/2
818  for i in range(NT2):
819  im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
820  im1[ipixes[:, 1], ipixes[:, 0]] = A[:, i]
821  plt.subplot(R, C, i+1)
822  plt.imshow(im1, interpolation='nearest', origin='lower')
823  plt.subplot(R, C, NT2+1)
824  im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
825  im1[ipixes[:, 1], ipixes[:, 0]] = b
826  plt.imshow(im1, interpolation='nearest', origin='lower')
827  plt.subplot(R, C, NT2+2)
828  im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
829  im1[ipixes[:, 1], ipixes[:, 0]] = w
830  plt.imshow(im1, interpolation='nearest', origin='lower')
831  plt.savefig('A.png')
832 
833  # We do fits with and without the decenter (dx,dy) terms.
834  # Since the dx,dy terms are at the end of the matrix,
835  # we can do that just by trimming off those elements.
836  #
837  # The SVD can fail if there are NaNs in the matrices; this should
838  # really be handled upstream
839  try:
840  # NT1 is number of terms without dx,dy;
841  # X1 is the result without decenter
842  X1, r1, rank1, s1 = np.linalg.lstsq(Aw[:, :NT1], bw)
843  # X2 is with decenter
844  X2, r2, rank2, s2 = np.linalg.lstsq(Aw, bw)
845  except np.linalg.LinAlgError as e:
846  log.log(log.WARN, "Failed to fit PSF to child: %s" % e)
847  pkres.setPsfFitFailed()
848  return
849 
850  log.debug('r1 r2 %s %s' % (r1, r2))
851 
852  # r is weighted chi-squared = sum over pixels: ramp * (model -
853  # data)**2/sigma**2
854  if len(r1) > 0:
855  chisq1 = r1[0]
856  else:
857  chisq1 = 1e30
858  if len(r2) > 0:
859  chisq2 = r2[0]
860  else:
861  chisq2 = 1e30
862  dof1 = sumr - len(X1)
863  dof2 = sumr - len(X2)
864  log.debug('dof1, dof2 %g %g' % (dof1, dof2))
865 
866  # This can happen if we're very close to the edge (?)
867  if dof1 <= 0 or dof2 <= 0:
868  log.trace('Skipping this peak: bad DOF %g, %g', dof1, dof2)
869  pkres.setBadPsfDof()
870  return
871 
872  q1 = chisq1/dof1
873  q2 = chisq2/dof2
874  log.trace('PSF fits: chisq/dof = %g, %g', q1, q2)
875  ispsf1 = (q1 < psfChisqCut1)
876  ispsf2 = (q2 < psfChisqCut2)
877 
878  pkres.psfFit1 = (chisq1, dof1)
879  pkres.psfFit2 = (chisq2, dof2)
880 
881  # check that the fit PSF spatial derivative terms aren't too big
882  if ispsf2:
883  fdx, fdy = X2[I_dx], X2[I_dy]
884  f0 = X2[I_psf]
885  # as a fraction of the PSF flux
886  dx = fdx/f0
887  dy = fdy/f0
888  ispsf2 = ispsf2 and (abs(dx) < 1. and abs(dy) < 1.)
889  log.trace('isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s',
890  dx, dy, str(ispsf2))
891  if not ispsf2:
892  pkres.psfFitBigDecenter = True
893 
894  # Looks like a shifted PSF: try actually shifting the PSF by that amount
895  # and re-evaluate the fit.
896  if ispsf2:
897  psfimg2 = psf.computeImage(cx + dx, cy + dy)
898  # clip
899  pbb2 = psfimg2.getBBox()
900  pbb2.clip(fbb)
901 
902  # Make sure we haven't been given a substitute PSF that's nowhere near where we want, as may occur if
903  # "Cannot compute CoaddPsf at point (xx,yy); no input images at that point."
904  if not pbb2.contains(afwGeom.Point2I(int(cx + dx), int(cy + dy))):
905  ispsf2 = False
906  else:
907  # clip image to bbox
908  px0, py0 = psfimg2.getX0(), psfimg2.getY0()
909  psfarr = psfimg2.getArray()[pbb2.getMinY()-py0:1+pbb2.getMaxY()-py0,
910  pbb2.getMinX()-px0:1+pbb2.getMaxX()-px0]
911  px0, py0 = pbb2.getMinX(), pbb2.getMinY()
912  px1, py1 = pbb2.getMaxX(), pbb2.getMaxY()
913 
914  # yuck! Update the PSF terms in the least-squares fit matrix.
915  Ab = A[:, :NT1]
916 
917  sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
918  sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
919  dpx0, dpy0 = px0 - xlo, py0 - ylo
920  psfsub = psfarr[sy3-dpy0:sy4-dpy0, sx3-dpx0:sx4-dpx0]
921  vsub = valid[sy1-ylo:sy2-ylo, sx1-xlo:sx2-xlo]
922  xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
923  inpsf = np.outer((yy >= py0)*(yy <= py1), (xx >= px0)*(xx <= px1))
924  Ab[inpsf[valid], I_psf] = psfsub[vsub]
925 
926  Aw = Ab*w[:, np.newaxis]
927  # re-solve...
928  Xb, rb, rankb, sb = np.linalg.lstsq(Aw, bw)
929  if len(rb) > 0:
930  chisqb = rb[0]
931  else:
932  chisqb = 1e30
933  dofb = sumr - len(Xb)
934  qb = chisqb/dofb
935  ispsf2 = (qb < psfChisqCut2b)
936  q2 = qb
937  X2 = Xb
938  log.trace('shifted PSF: new chisq/dof = %g; good? %s',
939  qb, ispsf2)
940  pkres.psfFit3 = (chisqb, dofb)
941 
942  # Which one do we keep?
943  if (((ispsf1 and ispsf2) and (q2 < q1)) or
944  (ispsf2 and not ispsf1)):
945  Xpsf = X2
946  chisq = chisq2
947  dof = dof2
948  log.debug('dof %g' % dof)
949  log.trace('Keeping shifted-PSF model')
950  cx += dx
951  cy += dy
952  pkres.psfFitWithDecenter = True
953  else:
954  # (arbitrarily set to X1 when neither fits well)
955  Xpsf = X1
956  chisq = chisq1
957  dof = dof1
958  log.debug('dof %g' % dof)
959  log.trace('Keeping unshifted PSF model')
960 
961  ispsf = (ispsf1 or ispsf2)
962 
963  # Save the PSF models in images for posterity.
964  if debugPsf:
965  SW, SH = 1+xhi-xlo, 1+yhi-ylo
966  psfmod = afwImage.ImageF(SW, SH)
967  psfmod.setXY0(xlo, ylo)
968  psfderivmodm = afwImage.MaskedImageF(SW, SH)
969  psfderivmod = psfderivmodm.getImage()
970  psfderivmod.setXY0(xlo, ylo)
971  model = afwImage.ImageF(SW, SH)
972  model.setXY0(xlo, ylo)
973  for i in range(len(Xpsf)):
974  for (x, y), v in zip(ipixes, A[:, i]*Xpsf[i]):
975  ix, iy = int(x), int(y)
976  model.set(ix, iy, model.get(ix, iy) + float(v))
977  if i in [I_psf, I_dx, I_dy]:
978  psfderivmod.set(ix, iy, psfderivmod.get(ix, iy) + float(v))
979  for ii in range(NP):
980  x, y = ipixes[ii, :]
981  psfmod.set(int(x), int(y), float(A[ii, I_psf]*Xpsf[I_psf]))
982  modelfp = afwDet.Footprint(fp.getPeaks().getSchema())
983  for (x, y) in ipixes:
984  modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
985  modelfp.normalize()
986 
987  pkres.psfFitDebugPsf0Img = psfimg
988  pkres.psfFitDebugPsfImg = psfmod
989  pkres.psfFitDebugPsfDerivImg = psfderivmod
990  pkres.psfFitDebugPsfModel = model
991  pkres.psfFitDebugStamp = img.Factory(img, stampbb, True)
992  pkres.psfFitDebugValidPix = valid # numpy array
993  pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb, True)
994  ww = np.zeros(valid.shape, np.float)
995  ww[valid] = w
996  pkres.psfFitDebugWeight = ww # numpy
997  pkres.psfFitDebugRampWeight = rw
998 
999  # Save things we learned about this peak for posterity...
1000  pkres.psfFitR0 = R0
1001  pkres.psfFitR1 = R1
1002  pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
1003  pkres.psfFitCenter = (cx, cy)
1004  log.debug('saving chisq,dof %g %g' % (chisq, dof))
1005  pkres.psfFitBest = (chisq, dof)
1006  pkres.psfFitParams = Xpsf
1007  pkres.psfFitFlux = Xpsf[I_psf]
1008  pkres.psfFitNOthers = len(otherpeaks)
1009 
1010  if ispsf:
1011  pkres.setDeblendedAsPsf()
1012 
1013  # replace the template image by the PSF + derivatives
1014  # image.
1015  log.trace('Deblending as PSF; setting template to PSF model')
1016 
1017  # Instantiate the PSF model and clip it to the footprint
1018  psfimg = psf.computeImage(cx, cy)
1019  # Scale by fit flux.
1020  psfimg *= Xpsf[I_psf]
1021  psfimg = psfimg.convertF()
1022 
1023  # Clip the Footprint to the PSF model image bbox.
1024  fpcopy = afwDet.Footprint(fp)
1025  psfbb = psfimg.getBBox()
1026  fpcopy.clipTo(psfbb)
1027  bb = fpcopy.getBBox()
1028 
1029  # Copy the part of the PSF model within the clipped footprint.
1030  psfmod = afwImage.ImageF(bb)
1031  afwDet.copyWithinFootprintImage(fpcopy, psfimg, psfmod)
1032  # Save it as our template.
1033  fpcopy.clipToNonzero(psfmod)
1034  fpcopy.normalize()
1035  pkres.setTemplate(psfmod, fpcopy)
1036 
1037  # DEBUG
1038  pkres.setPsfTemplate(psfmod, fpcopy)
1039 
1040 
1041 def _handle_flux_at_edge(log, psffwhm, t1, tfoot, fp, maskedImage,
1042  x0, x1, y0, y1, psf, pk, sigma1, patchEdges):
1043  # Import C++ routines
1044  import lsst.meas.deblender as deb
1045  butils = deb.BaselineUtilsF
1046 
1047  log.trace('Found significant flux at template edge.')
1048  # Compute the max of:
1049  # -symmetric-template-clipped image * PSF
1050  # -footprint-clipped image
1051  # Ie, extend the template by the PSF and "fill in" the footprint.
1052  # Then find the symmetric template of that image.
1053 
1054  # The size we'll grow by
1055  S = psffwhm*1.5
1056  # make it an odd integer
1057  S = int((S + 0.5)/2)*2 + 1
1058 
1059  tbb = tfoot.getBBox()
1060  tbb.grow(S)
1061 
1062  # (footprint+margin)-clipped image;
1063  # we need the pixels OUTSIDE the footprint to be 0.
1064  fpcopy = afwDet.Footprint(fp)
1065  fpcopy = afwDet.growFootprint(fpcopy, S)
1066  fpcopy.clipTo(tbb)
1067  fpcopy.normalize()
1068  padim = maskedImage.Factory(tbb)
1069  afwDet.copyWithinFootprintMaskedImage(fpcopy, maskedImage, padim)
1070 
1071  # find pixels on the edge of the template
1072  edgepix = butils.getSignificantEdgePixels(t1, tfoot, -1e6)
1073 
1074  # instantiate PSF image
1075  xc = int((x0 + x1)/2)
1076  yc = int((y0 + y1)/2)
1077  psfim = psf.computeImage(afwGeom.Point2D(xc, yc))
1078  pbb = psfim.getBBox()
1079  # shift PSF image to by centered on zero
1080  lx, ly = pbb.getMinX(), pbb.getMinY()
1081  psfim.setXY0(lx - xc, ly - yc)
1082  pbb = psfim.getBBox()
1083  # clip PSF to S, if necessary
1084  Sbox = afwGeom.Box2I(afwGeom.Point2I(-S, -S), afwGeom.Extent2I(2*S+1, 2*S+1))
1085  if not Sbox.contains(pbb):
1086  # clip PSF image
1087  psfim = psfim.Factory(psfim, Sbox, afwImage.PARENT, True)
1088  pbb = psfim.getBBox()
1089  px0 = pbb.getMinX()
1090  px1 = pbb.getMaxX()
1091  py0 = pbb.getMinY()
1092  py1 = pbb.getMaxY()
1093 
1094  # Compute the ramped-down edge pixels
1095  ramped = t1.Factory(tbb)
1096  Tout = ramped.getArray()
1097  Tin = t1.getArray()
1098  tx0, ty0 = t1.getX0(), t1.getY0()
1099  ox0, oy0 = ramped.getX0(), ramped.getY0()
1100  P = psfim.getArray()
1101  P /= P.max()
1102  # For each edge pixel, Tout = max(Tout, edgepix * PSF)
1103  for span in edgepix.getSpans():
1104  y = span.getY()
1105  for x in range(span.getX0(), span.getX1()+1):
1106  slc = (slice(y+py0 - oy0, y+py1+1 - oy0),
1107  slice(x+px0 - ox0, x+px1+1 - ox0))
1108  Tout[slc] = np.maximum(Tout[slc], Tin[y-ty0, x-tx0]*P)
1109 
1110  # Fill in the "padim" (which has the right variance and
1111  # mask planes) with the ramped pixels, outside the footprint
1112  I = (padim.getImage().getArray() == 0)
1113  padim.getImage().getArray()[I] = ramped.getArray()[I]
1114 
1115  rtn, patched = butils.buildSymmetricTemplate(padim, fpcopy, pk, sigma1, True, patchEdges)
1116  # silly SWIG can't unpack pairs as tuples
1117  t2, tfoot2 = rtn[0], rtn[1]
1118  del rtn
1119 
1120  # This template footprint may extend outside the parent
1121  # footprint -- or the image. Clip it.
1122  # NOTE that this may make it asymmetric, unlike normal templates.
1123  imbb = maskedImage.getBBox()
1124  tfoot2.clipTo(imbb)
1125  tbb = tfoot2.getBBox()
1126  # clip template image to bbox
1127  t2 = t2.Factory(t2, tbb, afwImage.PARENT, True)
1128 
1129  return t2, tfoot2, patched
def _fitPsf
Fit a PSF + smooth background model (linear) to a small region around the peak.
Definition: baseline.py:595
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.
In the PSF fitting code, we request PSF models for all peaks near the one being fit.
Definition: baseline.py:527
def _fitPsfs
Fit a PSF + smooth background models to a small region around each peak (plus other peaks that are ne...
Definition: baseline.py:553
def __init__
Creates a result object for the given parent Footprint &#39;fp&#39;.
Definition: baseline.py:45
An integer coordinate rectangle.
Definition: Box.h:53
Definition: Log.h:716
def getFluxPortion
Return a HeavyFootprint containing the flux apportioned to this peak.
Definition: baseline.py:159
def deblend
Deblend a single footprint in a maskedImage.
Definition: baseline.py:245
A set of pixels in an Image.
Definition: Footprint.h:62
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.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1107
Result of deblending a single Peak within a parent Footprint.
Definition: baseline.py:69
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)