lsst.meas.deblender  15.0+2
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 # Copyright 2008-2016 AURA/LSST.
9 #
10 # This product includes software developed by the
11 # LSST Project (http://www.lsst.org/).
12 #
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the LSST License Statement and
24 # the GNU General Public License along with this program. If not,
25 # see <https://www.lsstcorp.org/LegalNotices/>.
26 #
27 from collections import OrderedDict
28 import numpy as np
29 
31 import lsst.afw.image as afwImage
32 import lsst.afw.detection as afwDet
33 import lsst.afw.geom as afwGeom
34 import lsst.afw.math as afwMath
35 
36 from . import plugins
37 
38 DEFAULT_PLUGINS = [
39  plugins.DeblenderPlugin(plugins.fitPsfs),
40  plugins.DeblenderPlugin(plugins.buildSymmetricTemplates),
41  plugins.DeblenderPlugin(plugins.medianSmoothTemplates),
42  plugins.DeblenderPlugin(plugins.makeTemplatesMonotonic),
43  plugins.DeblenderPlugin(plugins.clipFootprintsToNonzero),
44  plugins.DeblenderPlugin(plugins.reconstructTemplates, onReset=5),
45  plugins.DeblenderPlugin(plugins.apportionFlux),
46 ]
47 
48 class DeblenderResult(object):
49  """Collection of objects in multiple bands for a single parent footprint
50  """
51 
52  def __init__(self, footprints, maskedImages, psfs, psffwhms, log, filters=None,
53  maxNumberOfPeaks=0, avgNoise=None):
54  """ Initialize a DeblededParent
55 
56  Parameters
57  ----------
58  footprint: list of `afw.detection.Footprint`s
59  Parent footprint to deblend in each band.
60  maskedImages: list of `afw.image.MaskedImageF`s
61  Masked image containing the ``footprint`` in each band.
62  psf: list of `afw.detection.Psf`s
63  Psf of the ``maskedImage`` for each band.
64  psffwhm: list of `float`s
65  FWHM of the ``maskedImage``'s ``psf`` in each band.
66  filters: list of `string`, optional
67  Names of the filters. This should be the same length as the other lists
68  maxNumberOfPeaks: `int`, optional
69  If positive, the maximum number of peaks to deblend.
70  If the total number of peaks is greater than ``maxNumberOfPeaks``,
71  then only the first ``maxNumberOfPeaks`` sources are deblended.
72  The default is 0, which deblends all of the peaks.
73  avgNoise: `float`or list of `float`s, optional
74  Average noise level in each ``maskedImage``.
75  The default is ``None``, which estimates the noise from the median value of the
76  variance plane of ``maskedImage`` for each filter.
77  Returns
78  -------
79  None
80  """
81  # Check if this is collection of footprints in multiple bands or a single footprint
82  try:
83  len(footprints)
84  except TypeError:
85  footprints = [footprints]
86  try:
87  len(maskedImages)
88  except TypeError:
89  maskedImages = [maskedImages]
90  try:
91  len(psfs)
92  except TypeError:
93  psfs = [psfs]
94  try:
95  len(psffwhms)
96  except TypeError:
97  psffwhms = [psffwhms]
98  try:
99  len(avgNoise)
100  except TypeError:
101  if avgNoise is None:
102  avgNoise = [None]*len(psfs)
103  else:
104  avgNoise = [avgNoise]
105  # Now check that all of the parameters have the same number of entries
106  if any([len(footprints)!=len(p) for p in [maskedImages, psfs, psffwhms, avgNoise]]):
107  raise ValueError("To use the multi-color deblender, "
108  "'footprint', 'maskedImage', 'psf', 'psffwhm', 'avgNoise'"
109  "must have the same length, but instead have lengths: "
110  "{0}".format([len(p) for p in [footprints,
111  maskedImages,
112  psfs,
113  psffwhms,
114  avgNoise]]))
115 
116  self.log = log
117  self.filterCount = len(footprints)
118 
119  self.peakCount = len(footprints[0].getPeaks())
120  if maxNumberOfPeaks>0 and maxNumberOfPeaks<self.peakCount:
121  self.peakCount = maxNumberOfPeaks
122 
123  if filters is None:
124  filters = range(len(footprints))
125  self.filters = filters
126 
127  # Create a DeblendedParent for the Footprint in every filter
128  self.deblendedParents = OrderedDict([])
129  for n in range(self.filterCount):
130  f = self.filters[n]
131  dp = DeblendedParent(f, footprints[n], maskedImages[n], psfs[n],
132  psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
133  self.deblendedParents[self.filters[n]] = dp
134 
135  # Group the peaks in each color
136  self.peaks = []
137  for idx in range(self.peakCount):
138  peakDict = {f: dp.peaks[idx] for f,dp in self.deblendedParents.items()}
139  multiPeak = MultiColorPeak(peakDict, idx, self)
140  self.peaks.append(multiPeak)
141 
142  def getParentProperty(self, propertyName):
143  """Get the footprint in each filter"""
144  return [getattr(fp, propertyName) for dp in self.deblendedParents]
145 
146  def setTemplateSums(self, templateSums, fidx=None):
147  if fidx is not None:
148  self.templateSums[fidx] = templateSums
149  else:
150  for f, templateSum in templateSums.items():
151  self.deblendedParents[f].templateSum = templateSum
152 
153 class DeblendedParent(object):
154  """Deblender result of a single parent footprint, in a single band
155 
156  Convenience class to store useful objects used by the deblender for a single band,
157  such as the maskedImage, psf, etc as well as the results from the deblender.
158  """
159  def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
160  maxNumberOfPeaks, debResult):
161  """Create a DeblendedParent to store a deblender result
162 
163  Parameters
164  ----------
165  filterName: `str`
166  Name of the filter used for `maskedImage`
167  footprint: list of `afw.detection.Footprint`s
168  Parent footprint to deblend in each band.
169  maskedImages: list of `afw.image.MaskedImageF`s
170  Masked image containing the ``footprint`` in each band.
171  psf: list of `afw.detection.Psf`s
172  Psf of the ``maskedImage`` for each band.
173  psffwhm: list of `float`s
174  FWHM of the ``maskedImage``'s ``psf`` in each band.
175  avgNoise: `float`or list of `float`s, optional
176  Average noise level in each ``maskedImage``.
177  The default is ``None``, which estimates the noise from the median value of the
178  variance plane of ``maskedImage`` for each filter.
179  maxNumberOfPeaks: `int`, optional
180  If positive, the maximum number of peaks to deblend.
181  If the total number of peaks is greater than ``maxNumberOfPeaks``,
182  then only the first ``maxNumberOfPeaks`` sources are deblended.
183  The default is 0, which deblends all of the peaks.
184  debResult: `DeblenderResult`
185  The ``DeblenderResult`` that contains this ``DeblendedParent``.
186  """
187  self.filter = filterName
188  self.fp = footprint
189  self.maskedImage = maskedImage
190  self.psf = psf
191  self.psffwhm = psffwhm
192  self.img = maskedImage.getImage()
193  self.imbb = self.img.getBBox()
194  self.varimg = maskedImage.getVariance()
195  self.mask = maskedImage.getMask()
196  self.avgNoise = avgNoise
197  self.updateFootprintBbox()
198  self.debResult = debResult
199  self.peakCount = debResult.peakCount
200  self.templateSum = None
201 
202  # avgNoise is an estiamte of the average noise level for the image in this filter
203  if avgNoise is None:
204  stats = afwMath.makeStatistics(self.varimg, self.mask, afwMath.MEDIAN)
205  avgNoise = np.sqrt(stats.getValue(afwMath.MEDIAN))
206  debResult.log.trace('Estimated avgNoise for filter %s = %f', self.filter, avgNoise)
207  self.avgNoise = avgNoise
208 
209  # Store all of the peak information in a single object
210  self.peaks = []
211  peaks = self.fp.getPeaks()
212  for idx in range(self.peakCount):
213  deblendedPeak = DeblendedPeak(peaks[idx], idx, self)
214  self.peaks.append(deblendedPeak)
215 
217  """Update the bounding box of the parent footprint
218 
219  If the parent Footprint is resized it will be necessary to update the bounding box of the footprint.
220  """
221  # Pull out the image bounds of the parent Footprint
222  self.bb = self.fp.getBBox()
223  if not self.imbb.contains(self.bb):
224  raise ValueError(('Footprint bounding-box %s extends outside image bounding-box %s') %
225  (str(self.bb), str(self.imbb)))
226  self.W, self.H = self.bb.getWidth(), self.bb.getHeight()
227  self.x0, self.y0 = self.bb.getMinX(), self.bb.getMinY()
228  self.x1, self.y1 = self.bb.getMaxX(), self.bb.getMaxY()
229 
230 class MultiColorPeak(object):
231  """Collection of single peak deblender results in multiple bands.
232 
233  There is one of these objects for each Peak in the footprint.
234  """
235 
236  def __init__(self, peaks, pki, parent):
237  """Create a collection for deblender results in each band.
238 
239  Parameters
240  ----------
241  peaks: `dict` of `afw.detection.PeakRecord`
242  Each entry in ``peaks`` is a peak record for the same peak in a different band
243  pki: int
244  Index of the peak in `parent.peaks`
245  parent: `DeblendedParent`
246  DeblendedParent object that contains the peak as a child.
247 
248  Returns
249  -------
250  None
251 
252  """
253  self.filters = list(peaks.keys())
254  self.deblendedPeaks = peaks
255  self.parent = parent
256  for pki, peak in self.deblendedPeaks.items():
257  peak.multiColorPeak = self
258 
259  # Fields common to the peak in all bands that will be set by the deblender
260  # In the future this is likely to contain information about the probability of the peak
261  # being a point source, color-information about templateFootprints, etc.
262  self.pki = pki
263  self.skip = False
264  self.deblendedAsPsf = False
265 
266 class DeblendedPeak(object):
267  """Result of deblending a single Peak within a parent Footprint.
268 
269  There is one of these objects for each Peak in the Footprint.
270  """
271 
272  def __init__(self, peak, pki, parent, multiColorPeak=None):
273  """Initialize a new deblended peak in a single filter band
274 
275  Parameters
276  ----------
277  peak: `afw.detection.PeakRecord`
278  Peak object in a single band from a peak record
279  pki: `int`
280  Index of the peak in `multiColorPeak.parent.peaks`
281  parent: `DeblendedParent`
282  Parent in the same filter that contains the peak
283  multiColorPeak: `MultiColorPeak`
284  Object containing the same peak in multiple bands
285 
286  Returns
287  -------
288  None
289  """
290  # Peak object
291  self.peak = peak
292  # int, peak index number
293  self.pki = pki
294  self.parent = parent
295  self.multiColorPeak = multiColorPeak
296  # union of all the ways of failing...
297  self.skip = False
298 
299  self.outOfBounds = False
300  self.tinyFootprint = False
301  self.noValidPixels = False
302  self.deblendedAsPsf = False
303  self.degenerate = False
304 
305  # Field set during _fitPsf:
306  self.psfFitFailed = False
307  self.psfFitBadDof = False
308  # (chisq, dof) for PSF fit without decenter
309  self.psfFit1 = None
310  # (chisq, dof) for PSF fit with decenter
311  self.psfFit2 = None
312  # (chisq, dof) for PSF fit after applying decenter
313  self.psfFit3 = None
314  # decentered PSF fit wanted to move the center too much
315  self.psfFitBigDecenter = False
316  # was the fit with decenter better?
317  self.psfFitWithDecenter = False
318  #
319  self.psfFitR0 = None
320  self.psfFitR1 = None
321  self.psfFitStampExtent = None
322  self.psfFitCenter = None
323  self.psfFitBest = None
324  self.psfFitParams = None
325  self.psfFitFlux = None
326  self.psfFitNOthers = None
327 
328  # Things only set in _fitPsf when debugging is turned on:
329  self.psfFitDebugPsf0Img = None
330  self.psfFitDebugPsfImg = None
333 
335 
336  # The actual template Image and Footprint
337  self.templateImage = None
338  self.templateFootprint = None
339 
340  # The flux assigned to this template -- a MaskedImage
341  self.fluxPortion = None
342 
343  # The stray flux assigned to this template (may be None), a HeavyFootprint
344  self.strayFlux = None
345 
346  self.hasRampedTemplate = False
347 
348  self.patched = False
349 
350  # debug -- a copy of the original symmetric template
351  self.origTemplate = None
352  self.origFootprint = None
353  # MaskedImage
354  self.rampedTemplate = None
355  # MaskedImage
357 
358  # when least-squares fitting templates, the template weight.
359  self.templateWeight = 1.0
360 
361  def __str__(self):
362  return (('deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
363  (self.outOfBounds, self.deblendedAsPsf))
364 
365  @property
366  def psfFitChisq(self):
367  chisq, dof = self.psfFitBest
368  return chisq
369 
370  @property
371  def psfFitDof(self):
372  chisq, dof = self.psfFitBest
373  return dof
374 
375  def getFluxPortion(self, strayFlux=True):
376  """!
377  Return a HeavyFootprint containing the flux apportioned to this peak.
378 
379  @param[in] strayFlux include stray flux also?
380  """
381  if self.templateFootprint is None or self.fluxPortion is None:
382  return None
383  heavy = afwDet.makeHeavyFootprint(self.templateFootprint, self.fluxPortion)
384  if strayFlux:
385  if self.strayFlux is not None:
386  heavy = afwDet.mergeHeavyFootprints(heavy, self.strayFlux)
387 
388  return heavy
389 
390  def setStrayFlux(self, stray):
391  self.strayFlux = stray
392 
393  def setFluxPortion(self, mimg):
394  self.fluxPortion = mimg
395 
396  def setTemplateWeight(self, w):
397  self.templateWeight = w
398 
399  def setPatched(self):
400  self.patched = True
401 
402  # DEBUG
403  def setOrigTemplate(self, t, tfoot):
404  self.origTemplate = t.Factory(t, True)
405  self.origFootprint = tfoot
406 
407  def setRampedTemplate(self, t, tfoot):
408  self.hasRampedTemplate = True
409  self.rampedTemplate = t.Factory(t, True)
410 
411  def setMedianFilteredTemplate(self, t, tfoot):
412  self.medianFilteredTemplate = t.Factory(t, True)
413 
414  def setPsfTemplate(self, tim, tfoot):
415  self.psfFootprint = afwDet.Footprint(tfoot)
416  self.psfTemplate = tim.Factory(tim, True)
417 
418  def setOutOfBounds(self):
419  self.outOfBounds = True
420  self.skip = True
421 
422  def setTinyFootprint(self):
423  self.tinyFootprint = True
424  self.skip = True
425 
426  def setNoValidPixels(self):
427  self.noValidPixels = True
428  self.skip = True
429 
430  def setPsfFitFailed(self):
431  self.psfFitFailed = True
432 
433  def setBadPsfDof(self):
434  self.psfFitBadDof = True
435 
436  def setDeblendedAsPsf(self):
437  self.deblendedAsPsf = True
438 
440  self.failedSymmetricTemplate = True
441  self.skip = True
442 
443  def setTemplate(self, image, footprint):
444  self.templateImage = image
445  self.templateFootprint = footprint
446 
447 def deblend(footprint, maskedImage, psf, psffwhm, filters=None,
448  psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=True,
449  medianSmoothTemplate=True, medianFilterHalfsize=2,
450  monotonicTemplate=True, weightTemplates=False,
451  log=None, verbose=False, sigma1=None, maxNumberOfPeaks=0,
452  assignStrayFlux=True, strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak',
453  rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2,
454  getTemplateSum=False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=True,
455  removeDegenerateTemplates=False, maxTempDotProd=0.5
456  ):
457  """Deblend a parent ``Footprint`` in a ``MaskedImageF``.
458 
459  Deblending assumes that ``footprint`` has multiple peaks, as it will still create a
460  `PerFootprint` object with a list of peaks even if there is only one peak in the list.
461  It is recommended to first check that ``footprint`` has more than one peak, similar to the
462  execution of `lsst.meas.deblender.deblend.SourceDeblendTask`.
463 
464  .. note::
465  This is the API for the old deblender, however the function builds the plugins necessary
466  to use the new deblender to perform identically to the old deblender.
467  To test out newer functionality use ``newDeblend`` instead.
468 
469  Deblending involves several mandatory and optional steps:
470  # Optional: If ``fitPsfs`` is True, find all peaks that are well-fit by a PSF + background model
471  * Peaks that pass the cuts have their footprints modified to the PSF + background model
472  and their ``deblendedAsPsf`` property set to ``True``.
473  * Relevant parameters: ``psfChisqCut1``, ``psfChisqCut2``, ``psfChisqCut2b``,
474  ``tinyFootprintSize``.
475  * See the parameter descriptions for more.
476  # Build a symmetric template for each peak not well-fit by the PSF model
477  * Given ``maskedImageF``, ``footprint``, and a ``DeblendedPeak``, creates a symmetric
478  template (``templateImage`` and ``templateFootprint``) around the peak
479  for all peaks not flagged as ``skip`` or ``deblendedAsPsf``.
480  * If ``patchEdges=True`` and if ``footprint`` touches pixels with the
481  ``EDGE`` bit set, then ``footprint`` is grown to include spans whose
482  symmetric mirror is outside of the image.
483  * Relevant parameters: ``sigma1`` and ``patchEdges``.
484  # Optional: If ``rampFluxAtEdge`` is True, adjust flux on the edges of the template footprints
485  * Using the PSF, a peak ``Footprint`` with pixels on the edge of of ``footprint``
486  is grown by the psffwhm*1.5 and filled in with zeros.
487  * The result is a new symmetric footprint template for the peaks near the edge.
488  * Relevant parameter: ``patchEdges``.
489  # Optionally (``medianSmoothTemplate=True``) filter the template images
490  * Apply a median smoothing filter to all of the template images.
491  * Relevant parameters: ``medianFilterHalfSize``
492  # Optional: If ``monotonicTemplate`` is True, make the templates monotonic.
493  * The pixels in the templates are modified such that pixels
494  further from the peak will have values smaller than those closer to the peak.
495  # Optional: If ``clipFootprintToNonzero`` is True, clip non-zero spans in the template footprints
496  * Peak ``Footprint``s are clipped to the region in the image containing non-zero values
497  by dropping spans that are completely zero and moving endpoints to non-zero pixels
498  (but does not split spans that have internal zeros).
499  # Optional: If ``weightTemplates`` is True, weight the templates to best fit the observed image
500  * Re-weight the templates so that their linear combination
501  best represents the observed ``maskedImage``
502  # Optional: If ``removeDegenerateTempaltes`` is True, reconstruct shredded galaxies
503  * If galaxies have substructure, such as face-on spirals, the process of identifying peaks can
504  "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite
505  similar because they represent the same galaxy, so we try to identify these "degenerate" peaks
506  by looking at the inner product (in pixel space) of pairs of templates.
507  * If they are nearly parallel, we only keep one of the peaks and reject the other.
508  * If only one of the peaks is a PSF template, the other template is used,
509  otherwise the one with the maximum template value is kept.
510  * Relevant parameters: ``maxTempDotProduct``
511  # Apportion flux to all of the peak templates
512  * Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of
513  flux assigned to each ``tempalteFootprint``.
514  * Leftover "stray flux" is assigned to peaks based on the other parameters.
515  * Relevant parameters: ``clipStrayFluxFraction``, ``strayFluxAssignment``,
516  ``strayFluxToPointSources``, ``assignStrayFlux``
517 
518  Parameters
519  ----------
520  footprint: `afw.detection.Footprint`
521  Parent footprint to deblend
522  maskedImage: `afw.image.MaskedImageF`
523  Masked image containing the ``footprint``
524  psf: `afw.detection.Psf`
525  Psf of the ``maskedImage``
526  psffwhm: `float`
527  FWHM of the ``maskedImage``'s ``psf``
528  psfChisqCut*: `float`, optional
529  If ``fitPsfs==True``, all of the peaks are fit to the image PSF.
530  ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to
531  be considered a PSF match without recentering.
532  A fit is also made that includes terms to recenter the PSF.
533  ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the
534  fit that includes recentering terms.
535  If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and
536  the peak footprint is fit again, this time to the new PSF.
537  If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it
538  passes the re-centering algorithm.
539  If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted,
540  but parameters for all three psf fits are stored in the ``DeblendedPeak``.
541  The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``.
542  fitPsfs: `bool`, optional
543  If True then all of the peaks will be compared to the image PSF to
544  distinguish stars from galaxies.
545  medianSmoothTemplate: ``bool``, optional
546  If ``medianSmoothTemplate==True`` it a median smoothing filter is applied to the ``maskedImage``.
547  The default is ``True``.
548  medianFilterHalfSize: `int`, optional
549  Half the box size of the median filter, ie a ``medianFilterHalfSize`` of 50 means that
550  each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image.
551  This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored.
552  The default value is 2.
553  monotonicTempalte: `bool`, optional
554  If True then make the template monotonic.
555  The default is True.
556  weightTemplates: `bool`, optional
557  If True, re-weight the templates so that their linear combination best represents
558  the observed ``maskedImage``.
559  The default is False.
560  log: `log.Log`, optional
561  LSST logger for logging purposes.
562  The default is ``None`` (no logging).
563  verbose: `bool`, optional
564  Whether or not to show a more verbose output.
565  The default is ``False``.
566  sigma1: `float`, optional
567  Average noise level in ``maskedImage``.
568  The default is ``None``, which estimates the noise from the median value of ``maskedImage``.
569  maxNumberOfPeaks: `int`, optional
570  If nonzero, the maximum number of peaks to deblend.
571  If the total number of peaks is greater than ``maxNumberOfPeaks``,
572  then only the first ``maxNumberOfPeaks`` sources are deblended.
573  The default is 0, which deblends all of the peaks.
574  assignStrayFlux: `bool`, optional
575  If True then flux in the parent footprint that is not covered by any of the
576  template footprints is assigned to templates based on their 1/(1+r^2) distance.
577  How the flux is apportioned is determined by ``strayFluxAssignment``.
578  The default is True.
579  strayFluxToPointSources: `string`
580  Determines how stray flux is apportioned to point sources
581  * ``never``: never apportion stray flux to point sources
582  * ``necessary`` (default): point sources are included only if there are no extended sources nearby
583  * ``always``: point sources are always included in the 1/(1+r^2) splitting
584  strayFluxAssignment: `string`, optional
585  Determines how stray flux is apportioned.
586  * ``trim``: Trim stray flux and do not include in any footprints
587  * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks
588  * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the
589  minimum distance from the stray flux to footprint
590  * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan)
591  distance to the stray flux
592  rampFluxAtEdge: `bool`, optional
593  If True then extend footprints with excessive flux on the edges as described above.
594  The default is False.
595  patchEdges: `bool`, optional
596  If True and if the footprint touches pixels with the ``EDGE`` bit set,
597  then grow the footprint to include all symmetric templates.
598  The default is ``False``.
599  tinyFootprintSize: `float`, optional
600  The PSF model is shrunk to the size that contains the original footprint.
601  If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)``
602  then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit.
603  The default is 2.
604  getTemplateSum: `bool`, optional
605  As part of the flux calculation, the sum of the templates is calculated.
606  If ``getTemplateSum==True`` then the sum of the templates is stored in the result (a `PerFootprint`).
607  The default is False.
608  clipStrayFluxFraction: `float`, optional
609  Minimum stray-flux portion.
610  Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero.
611  The default is 0.001.
612  clipFootprintToNonzero: `bool`, optional
613  If True then clip non-zero spans in the template footprints. See above for more.
614  The default is True.
615  removeDegenerateTemplates: `bool`, optional
616  If True then we try to identify "degenerate" peaks by looking at the inner product
617  (in pixel space) of pairs of templates.
618  The default is False.
619  maxTempDotProduct: `float`, optional
620  All dot products between templates greater than ``maxTempDotProduct`` will result in one
621  of the templates removed. This parameter is only used when ``removeDegenerateTempaltes==True``.
622  The default is 0.5.
623 
624  Returns
625  -------
626  res: `PerFootprint`
627  Deblender result that contains a list of ``DeblendedPeak``s for each peak and (optionally)
628  the template sum.
629  """
630  avgNoise = sigma1
631 
632  debPlugins = []
633 
634  # Add activated deblender plugins
635  if fitPsfs:
636  debPlugins.append(plugins.DeblenderPlugin(plugins.fitPsfs,
637  psfChisqCut1=psfChisqCut1,
638  psfChisqCut2=psfChisqCut2,
639  psfChisqCut2b=psfChisqCut2b,
640  tinyFootprintSize=tinyFootprintSize))
641  debPlugins.append(plugins.DeblenderPlugin(plugins.buildSymmetricTemplates, patchEdges=patchEdges))
642  if rampFluxAtEdge:
643  debPlugins.append(plugins.DeblenderPlugin(plugins.rampFluxAtEdge, patchEdges=patchEdges))
644  if medianSmoothTemplate:
645  debPlugins.append(plugins.DeblenderPlugin(plugins.medianSmoothTemplates,
646  medianFilterHalfsize=medianFilterHalfsize))
647  if monotonicTemplate:
648  debPlugins.append(plugins.DeblenderPlugin(plugins.makeTemplatesMonotonic))
649  if clipFootprintToNonzero:
650  debPlugins.append(plugins.DeblenderPlugin(plugins.clipFootprintsToNonzero))
651  if weightTemplates:
652  debPlugins.append(plugins.DeblenderPlugin(plugins.weightTemplates))
653  if removeDegenerateTemplates:
654  if weightTemplates:
655  onReset = len(debPlugins)-1
656  else:
657  onReset = len(debPlugins)
658  debPlugins.append(plugins.DeblenderPlugin(plugins.reconstructTemplates,
659  onReset=onReset,
660  maxTempDotProd=maxTempDotProd))
661  debPlugins.append(plugins.DeblenderPlugin(plugins.apportionFlux,
662  clipStrayFluxFraction=clipStrayFluxFraction,
663  assignStrayFlux=assignStrayFlux,
664  strayFluxAssignment=strayFluxAssignment,
665  strayFluxToPointSources=strayFluxToPointSources,
666  getTemplateSum=getTemplateSum))
667 
668  debResult = newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters, log, verbose, avgNoise)
669 
670  return debResult
671 
672 def newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters=None,
673  log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0):
674  """Deblend a parent ``Footprint`` in a ``MaskedImageF``.
675 
676  Deblending assumes that ``footprint`` has multiple peaks, as it will still create a
677  `PerFootprint` object with a list of peaks even if there is only one peak in the list.
678  It is recommended to first check that ``footprint`` has more than one peak, similar to the
679  execution of `lsst.meas.deblender.deblend.SourceDeblendTask`.
680 
681  This version of the deblender accepts a list of plugins to execute, with the option to re-run parts
682  of the deblender if templates are changed during any of the steps.
683 
684  Parameters
685  ----------
686  debPlugins: list of `meas.deblender.plugins.DeblenderPlugins`
687  Plugins to execute (in order of execution) for the deblender.
688  footprint: `afw.detection.Footprint` or list of Footprints
689  Parent footprint to deblend.
690  maskedImage: `afw.image.MaskedImageF` or list of MaskedImages
691  Masked image containing the ``footprint``.
692  psf: `afw.detection.Psf` or list of Psfs
693  Psf of the ``maskedImage``.
694  psffwhm: `float` or list of floats
695  FWHM of the ``maskedImage``'s ``psf``.
696  filters: list of `string`s, optional
697  Names of the filters when ``footprint`` is a list instead of a single ``footprint``.
698  This is used to index the deblender results by filter.
699  The default is None, which will numerically index lists of objects.
700  log: `log.Log`, optional
701  LSST logger for logging purposes.
702  The default is ``None`` (no logging).
703  verbose: `bool`, optional
704  Whether or not to show a more verbose output.
705  The default is ``False``.
706  avgNoise: `float`or list of `float`s, optional
707  Average noise level in each ``maskedImage``.
708  The default is ``None``, which estimates the noise from the median value of the
709  variance plane of ``maskedImage`` for each filter.
710  maxNumberOfPeaks: `int`, optional
711  If nonzero, the maximum number of peaks to deblend.
712  If the total number of peaks is greater than ``maxNumberOfPeaks``,
713  then only the first ``maxNumberOfPeaks`` sources are deblended.
714 
715  Returns
716  -------
717  debResult: `DeblendedParent`
718  Deblender result that contains a list of ``MultiColorPeak``s for each peak and
719  information that describes the footprint in all filters.
720  """
721  # Import C++ routines
722  import lsst.meas.deblender as deb
723  butils = deb.BaselineUtilsF
724 
725  if log is None:
726  import lsst.log as lsstLog
727 
728  component = 'meas_deblender.baseline'
729  log = lsstLog.Log.getLogger(component)
730 
731  if verbose:
732  log.setLevel(lsstLog.Log.TRACE)
733 
734  # get object that will hold our results
735  debResult = DeblenderResult(footprint, maskedImage, psf, psffwhm, log, filters=filters,
736  maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
737 
738  step = 0
739  while step < len(debPlugins):
740  reset = debPlugins[step].run(debResult, log)
741  if reset:
742  step = debPlugins[step].onReset
743  else:
744  step+=1
745 
746  return debResult
747 
748 
749 class CachingPsf(object):
750  """Cache the PSF models
751 
752  In the PSF fitting code, we request PSF models for all peaks near
753  the one being fit. This was turning out to be quite expensive in
754  some cases. Here, we cache the PSF models to bring the cost down
755  closer to O(N) rather than O(N^2).
756  """
757 
758  def __init__(self, psf):
759  self.cache = {}
760  self.psf = psf
761 
762  def computeImage(self, cx, cy):
763  im = self.cache.get((cx, cy), None)
764  if im is not None:
765  return im
766  try:
767  im = self.psf.computeImage(afwGeom.Point2D(cx, cy))
769  im = self.psf.computeImage()
770  self.cache[(cx, cy)] = im
771  return im
def setTemplate(self, image, footprint)
Definition: baseline.py:443
def newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters=None, log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0)
Definition: baseline.py:673
def deblend(footprint, maskedImage, psf, psffwhm, filters=None, 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, assignStrayFlux=True, strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak', rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2, getTemplateSum=False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=True, removeDegenerateTemplates=False, maxTempDotProd=0.5)
Definition: baseline.py:456
def setMedianFilteredTemplate(self, t, tfoot)
Definition: baseline.py:411
def __init__(self, peak, pki, parent, multiColorPeak=None)
Definition: baseline.py:272
def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise, maxNumberOfPeaks, debResult)
Definition: baseline.py:160
def __init__(self, peaks, pki, parent)
Definition: baseline.py:236
def setTemplateSums(self, templateSums, fidx=None)
Definition: baseline.py:146
def getFluxPortion(self, strayFlux=True)
Return a HeavyFootprint containing the flux apportioned to this peak.
Definition: baseline.py:375
def getParentProperty(self, propertyName)
Definition: baseline.py:142
def __init__(self, footprints, maskedImages, psfs, psffwhms, log, filters=None, maxNumberOfPeaks=0, avgNoise=None)
Definition: baseline.py:53