22 from collections 
import OrderedDict
 
   45     """Collection of objects in multiple bands for a single parent footprint 
   48     def __init__(self, footprint, mMaskedImage, psfs, psffwhms, log,
 
   49                  maxNumberOfPeaks=0, avgNoise=None):
 
   50         """ Initialize a DeblededParent 
   54         footprint: `afw.detection.Footprint` 
   55             Parent footprint to deblend. While `maskedImages`, `psfs`, 
   56             and `psffwhms` are lists of objects, one for each band, 
   57             `footprint` is a single parent footprint (from a `mergedDet`) 
   58             this is used for all bands. 
   59         mMaskedImage: `MaskedImage`s or `MultibandMaskedImage` 
   60             Masked image containing the ``footprint`` in each band. 
   61         psfs: list of `afw.detection.Psf`s 
   62             Psf of the ``maskedImage`` for each band. 
   63         psffwhm: list of `float`s 
   64             FWHM of the ``maskedImage``'s ``psf`` in each band. 
   65         maxNumberOfPeaks: `int`, optional 
   66             If positive, the maximum number of peaks to deblend. 
   67             If the total number of peaks is greater than ``maxNumberOfPeaks``, 
   68             then only the first ``maxNumberOfPeaks`` sources are deblended. 
   69             The default is 0, which deblends all of the peaks. 
   70         avgNoise: `float`or list of `float`s, optional 
   71             Average noise level in each ``maskedImage``. 
   72             The default is ``None``, which estimates the noise from the median value of the 
   73             variance plane of ``maskedImage`` for each filter. 
   80             mMaskedImage = [mMaskedImage]
 
   83             self.
filters = mMaskedImage.filters
 
   96                 avgNoise = [
None]*len(psfs)
 
  100         if any([len(self.
filters) != len(p) 
for p 
in [psfs, psffwhms, avgNoise]]):
 
  101             raise ValueError(
"To use the multi-color deblender, " 
  102                              "'maskedImage', 'psf', 'psffwhm', 'avgNoise'" 
  103                              "must have the same length, but instead have lengths: " 
  104                              "{0}".
format([len(p) 
for p 
in [mMaskedImage,
 
  114         self.
peakCount = len(footprint.getPeaks())
 
  115         if maxNumberOfPeaks > 0 
and maxNumberOfPeaks < self.
peakCount:
 
  120         for n, f 
in enumerate(self.
filters):
 
  123                                  psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
 
  138         """Get the footprint in each filter""" 
  143             self.templateSums[fidx] = templateSums
 
  145             for f, templateSum 
in templateSums.items():
 
  150     """Deblender result of a single parent footprint, in a single band 
  152     Convenience class to store useful objects used by the deblender for a single band, 
  153     such as the maskedImage, psf, etc as well as the results from the deblender. 
  155     def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
 
  156                  maxNumberOfPeaks, debResult):
 
  157         """Create a DeblendedParent to store a deblender result 
  162             Name of the filter used for `maskedImage` 
  163         footprint: list of `afw.detection.Footprint`s 
  164             Parent footprint to deblend in each band. 
  165         maskedImages: list of `afw.image.MaskedImageF`s 
  166             Masked image containing the ``footprint`` in each band. 
  167         psf: list of `afw.detection.Psf`s 
  168             Psf of the ``maskedImage`` for each band. 
  169         psffwhm: list of `float`s 
  170             FWHM of the ``maskedImage``'s ``psf`` in each band. 
  171         avgNoise: `float`or list of `float`s, optional 
  172             Average noise level in each ``maskedImage``. 
  173             The default is ``None``, which estimates the noise from the median value of the 
  174             variance plane of ``maskedImage`` for each filter. 
  175         maxNumberOfPeaks: `int`, optional 
  176             If positive, the maximum number of peaks to deblend. 
  177             If the total number of peaks is greater than ``maxNumberOfPeaks``, 
  178             then only the first ``maxNumberOfPeaks`` sources are deblended. 
  179             The default is 0, which deblends all of the peaks. 
  180         debResult: `DeblenderResult` 
  181             The ``DeblenderResult`` that contains this ``DeblendedParent``. 
  188         self.
img = maskedImage.getImage()
 
  191         self.
mask = maskedImage.getMask()
 
  201             avgNoise = np.sqrt(stats.getValue(afwMath.MEDIAN))
 
  202             debResult.log.trace(
'Estimated avgNoise for filter %s = %f', self.
filter, avgNoise)
 
  207         peaks = self.
fp.getPeaks()
 
  213         """Update the bounding box of the parent footprint 
  215         If the parent Footprint is resized it will be necessary to update the bounding box of the footprint. 
  220             raise ValueError((
'Footprint bounding-box %s extends outside image bounding-box %s') %
 
  221                              (str(self.
bb), str(self.
imbb)))
 
  222         self.W, self.
H = self.
bb.getWidth(), self.
bb.getHeight()
 
  223         self.x0, self.
y0 = self.
bb.getMinX(), self.
bb.getMinY()
 
  224         self.x1, self.
y1 = self.
bb.getMaxX(), self.
bb.getMaxY()
 
  228     """Collection of single peak deblender results in multiple bands. 
  230     There is one of these objects for each Peak in the footprint. 
  234         """Create a collection for deblender results in each band. 
  238         peaks: `dict` of `afw.detection.PeakRecord` 
  239             Each entry in ``peaks`` is a peak record for the same peak in a different band 
  241             Index of the peak in `parent.peaks` 
  242         parent: `DeblendedParent` 
  243             DeblendedParent object that contains the peak as a child. 
  254             peak.multiColorPeak = self
 
  267     """Result of deblending a single Peak within a parent Footprint. 
  269     There is one of these objects for each Peak in the Footprint. 
  272     def __init__(self, peak, pki, parent, multiColorPeak=None):
 
  273         """Initialize a new deblended peak in a single filter band 
  277         peak: `afw.detection.PeakRecord` 
  278             Peak object in a single band from a peak record 
  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 
  362         return ((
'deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
 
  377         Return a HeavyFootprint containing the flux apportioned to this peak. 
  379         @param[in]     strayFlux   include stray flux also? 
  448 def deblend(footprint, maskedImage, psf, psffwhm,
 
  449             psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=True,
 
  450             medianSmoothTemplate=True, medianFilterHalfsize=2,
 
  451             monotonicTemplate=True, weightTemplates=False,
 
  452             log=None, verbose=False, sigma1=None, maxNumberOfPeaks=0,
 
  453             assignStrayFlux=True, strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak',
 
  454             rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2,
 
  455             getTemplateSum=False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=True,
 
  456             removeDegenerateTemplates=False, maxTempDotProd=0.5
 
  458     """Deblend a parent ``Footprint`` in a ``MaskedImageF``. 
  460     Deblending assumes that ``footprint`` has multiple peaks, as it will still create a 
  461     `PerFootprint` object with a list of peaks even if there is only one peak in the list. 
  462     It is recommended to first check that ``footprint`` has more than one peak, similar to the 
  463     execution of `lsst.meas.deblender.deblend.SourceDeblendTask`. 
  466         This is the API for the old deblender, however the function builds the plugins necessary 
  467         to use the new deblender to perform identically to the old deblender. 
  468         To test out newer functionality use ``newDeblend`` instead. 
  470     Deblending involves several mandatory and optional steps: 
  471     # Optional: If ``fitPsfs`` is True, find all peaks that are well-fit by a PSF + background model 
  472         * Peaks that pass the cuts have their footprints modified to the PSF + background model 
  473           and their ``deblendedAsPsf`` property set to ``True``. 
  474         * Relevant parameters: ``psfChisqCut1``, ``psfChisqCut2``, ``psfChisqCut2b``, 
  475           ``tinyFootprintSize``. 
  476         * See the parameter descriptions for more. 
  477     # Build a symmetric template for each peak not well-fit by the PSF model 
  478         * Given ``maskedImageF``, ``footprint``, and a ``DeblendedPeak``, creates a symmetric 
  479           template (``templateImage`` and ``templateFootprint``) around the peak 
  480           for all peaks not flagged as ``skip`` or ``deblendedAsPsf``. 
  481         * If ``patchEdges=True`` and if ``footprint`` touches pixels with the 
  482           ``EDGE`` bit set, then ``footprint`` is grown to include spans whose 
  483           symmetric mirror is outside of the image. 
  484         * Relevant parameters: ``sigma1`` and ``patchEdges``. 
  485     # Optional: If ``rampFluxAtEdge`` is True, adjust flux on the edges of the template footprints 
  486         * Using the PSF, a peak ``Footprint`` with pixels on the edge of of ``footprint`` 
  487           is grown by the psffwhm*1.5 and filled in with zeros. 
  488         * The result is a new symmetric footprint template for the peaks near the edge. 
  489         * Relevant parameter: ``patchEdges``. 
  490     # Optionally (``medianSmoothTemplate=True``) filter the template images 
  491         * Apply a median smoothing filter to all of the template images. 
  492         * Relevant parameters: ``medianFilterHalfSize`` 
  493     # Optional: If ``monotonicTemplate`` is True, make the templates monotonic. 
  494         * The pixels in the templates are modified such that pixels 
  495           further from the peak will have values smaller than those closer to the peak. 
  496     # Optional: If ``clipFootprintToNonzero`` is True, clip non-zero spans in the template footprints 
  497         * Peak ``Footprint``s are clipped to the region in the image containing non-zero values 
  498           by dropping spans that are completely zero and moving endpoints to non-zero pixels 
  499           (but does not split spans that have internal zeros). 
  500     # Optional: If ``weightTemplates`` is True,  weight the templates to best fit the observed image 
  501         * Re-weight the templates so that their linear combination 
  502           best represents the observed ``maskedImage`` 
  503     # Optional: If ``removeDegenerateTempaltes`` is True, reconstruct shredded galaxies 
  504         * If galaxies have substructure, such as face-on spirals, the process of identifying peaks can 
  505           "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite 
  506           similar because they represent the same galaxy, so we try to identify these "degenerate" peaks 
  507           by looking at the inner product (in pixel space) of pairs of templates. 
  508         * If they are nearly parallel, we only keep one of the peaks and reject the other. 
  509         * If only one of the peaks is a PSF template, the other template is used, 
  510           otherwise the one with the maximum template value is kept. 
  511         * Relevant parameters: ``maxTempDotProduct`` 
  512     # Apportion flux to all of the peak templates 
  513         * Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of 
  514           flux assigned to each ``tempalteFootprint``. 
  515         * Leftover "stray flux" is assigned to peaks based on the other parameters. 
  516         * Relevant parameters: ``clipStrayFluxFraction``, ``strayFluxAssignment``, 
  517           ``strayFluxToPointSources``, ``assignStrayFlux`` 
  521     footprint: `afw.detection.Footprint` 
  522         Parent footprint to deblend 
  523     maskedImage: `afw.image.MaskedImageF` 
  524         Masked image containing the ``footprint`` 
  525     psf: `afw.detection.Psf` 
  526         Psf of the ``maskedImage`` 
  528         FWHM of the ``maskedImage``'s ``psf`` 
  529     psfChisqCut*: `float`, optional 
  530         If ``fitPsfs==True``, all of the peaks are fit to the image PSF. 
  531         ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to 
  532         be considered a PSF match without recentering. 
  533         A fit is also made that includes terms to recenter the PSF. 
  534         ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the 
  535         fit that includes recentering terms. 
  536         If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and 
  537         the peak footprint is fit again, this time to the new PSF. 
  538         If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it 
  539         passes the re-centering algorithm. 
  540         If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted, 
  541         but parameters for all three psf fits are stored in the ``DeblendedPeak``. 
  542         The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``. 
  543     fitPsfs: `bool`, optional 
  544         If True then all of the peaks will be compared to the image PSF to 
  545         distinguish stars from galaxies. 
  546     medianSmoothTemplate: ``bool``, optional 
  547         If ``medianSmoothTemplate==True`` it a median smoothing filter is applied to the ``maskedImage``. 
  548         The default is ``True``. 
  549     medianFilterHalfSize: `int`, optional 
  550         Half the box size of the median filter, ie a ``medianFilterHalfSize`` of 50 means that 
  551         each output pixel will be the median of  the pixels in a 101 x 101-pixel box in the input image. 
  552         This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored. 
  553         The default value is 2. 
  554     monotonicTempalte: `bool`, optional 
  555         If True then make the template monotonic. 
  557     weightTemplates: `bool`, optional 
  558         If True, re-weight the templates so that their linear combination best represents 
  559         the observed ``maskedImage``. 
  560         The default is False. 
  561     log: `log.Log`, optional 
  562         LSST logger for logging purposes. 
  563         The default is ``None`` (no logging). 
  564     verbose: `bool`, optional 
  565         Whether or not to show a more verbose output. 
  566         The default is ``False``. 
  567     sigma1: `float`, optional 
  568         Average noise level in ``maskedImage``. 
  569         The default is ``None``, which estimates the noise from the median value of ``maskedImage``. 
  570     maxNumberOfPeaks: `int`, optional 
  571         If nonzero, the maximum number of peaks to deblend. 
  572         If the total number of peaks is greater than ``maxNumberOfPeaks``, 
  573         then only the first ``maxNumberOfPeaks`` sources are deblended. 
  574         The default is 0, which deblends all of the peaks. 
  575     assignStrayFlux: `bool`, optional 
  576         If True then flux in the parent footprint that is not covered by any of the 
  577         template footprints is assigned to templates based on their 1/(1+r^2) distance. 
  578         How the flux is apportioned is determined by ``strayFluxAssignment``. 
  580     strayFluxToPointSources: `string` 
  581         Determines how stray flux is apportioned to point sources 
  582         * ``never``: never apportion stray flux to point sources 
  583         * ``necessary`` (default): point sources are included only if there are no extended sources nearby 
  584         * ``always``: point sources are always included in the 1/(1+r^2) splitting 
  585     strayFluxAssignment: `string`, optional 
  586         Determines how stray flux is apportioned. 
  587         * ``trim``: Trim stray flux and do not include in any footprints 
  588         * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks 
  589         * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the 
  590           minimum distance from the stray flux to footprint 
  591         * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan) 
  592           distance to the stray flux 
  593     rampFluxAtEdge: `bool`, optional 
  594         If True then extend footprints with excessive flux on the edges as described above. 
  595         The default is False. 
  596     patchEdges: `bool`, optional 
  597         If True and if the footprint touches pixels with the ``EDGE`` bit set, 
  598         then grow the footprint to include all symmetric templates. 
  599         The default is ``False``. 
  600     tinyFootprintSize: `float`, optional 
  601         The PSF model is shrunk to the size that contains the original footprint. 
  602         If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)`` 
  603         then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit. 
  605     getTemplateSum: `bool`, optional 
  606         As part of the flux calculation, the sum of the templates is calculated. 
  607         If ``getTemplateSum==True`` then the sum of the templates is stored in the result (a `PerFootprint`). 
  608         The default is False. 
  609     clipStrayFluxFraction: `float`, optional 
  610         Minimum stray-flux portion. 
  611         Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero. 
  612         The default is 0.001. 
  613     clipFootprintToNonzero: `bool`, optional 
  614         If True then clip non-zero spans in the template footprints. See above for more. 
  616     removeDegenerateTemplates: `bool`, optional 
  617         If True then we try to identify "degenerate" peaks by looking at the inner product 
  618         (in pixel space) of pairs of templates. 
  619         The default is False. 
  620     maxTempDotProduct: `float`, optional 
  621         All dot products between templates greater than ``maxTempDotProduct`` will result in one 
  622         of the templates removed. This parameter is only used when ``removeDegenerateTempaltes==True``. 
  628         Deblender result that contains a list of ``DeblendedPeak``s for each peak and (optionally) 
  638                                                   psfChisqCut1=psfChisqCut1,
 
  639                                                   psfChisqCut2=psfChisqCut2,
 
  640                                                   psfChisqCut2b=psfChisqCut2b,
 
  641                                                   tinyFootprintSize=tinyFootprintSize))
 
  645     if medianSmoothTemplate:
 
  647                                                   medianFilterHalfsize=medianFilterHalfsize))
 
  648     if monotonicTemplate:
 
  650     if clipFootprintToNonzero:
 
  654     if removeDegenerateTemplates:
 
  656             onReset = len(debPlugins)-1
 
  658             onReset = len(debPlugins)
 
  661                                                   maxTempDotProd=maxTempDotProd))
 
  663                                               clipStrayFluxFraction=clipStrayFluxFraction,
 
  664                                               assignStrayFlux=assignStrayFlux,
 
  665                                               strayFluxAssignment=strayFluxAssignment,
 
  666                                               strayFluxToPointSources=strayFluxToPointSources,
 
  667                                               getTemplateSum=getTemplateSum))
 
  669     debResult = 
newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, log, verbose, avgNoise)
 
  674 def newDeblend(debPlugins, footprint, mMaskedImage, psfs, psfFwhms,
 
  675                log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0):
 
  676     """Deblend a parent ``Footprint`` in a ``MaskedImageF``. 
  678     Deblending assumes that ``footprint`` has multiple peaks, as it will still create a 
  679     `PerFootprint` object with a list of peaks even if there is only one peak in the list. 
  680     It is recommended to first check that ``footprint`` has more than one peak, similar to the 
  681     execution of `lsst.meas.deblender.deblend.SourceDeblendTask`. 
  683     This version of the deblender accepts a list of plugins to execute, with the option to re-run parts 
  684     of the deblender if templates are changed during any of the steps. 
  688     debPlugins: list of `meas.deblender.plugins.DeblenderPlugins` 
  689         Plugins to execute (in order of execution) for the deblender. 
  690     footprint: `afw.detection.Footprint` or list of Footprints 
  691         Parent footprint to deblend. 
  692     mMaskedImage: `MultibandMaskedImage` or `MaskedImage` 
  693         Masked image in each band. 
  694     psfs: `afw.detection.Psf` or list of Psfs 
  695         Psf of the ``maskedImage``. 
  696     psfFwhms: `float` or list of floats 
  697         FWHM of the ``maskedImage``'s ``psf``. 
  698     log: `log.Log`, optional 
  699         LSST logger for logging purposes. 
  700         The default is ``None`` (no logging). 
  701     verbose: `bool`, optional 
  702         Whether or not to show a more verbose output. 
  703         The default is ``False``. 
  704     avgNoise: `float`or list of `float`s, optional 
  705         Average noise level in each ``maskedImage``. 
  706         The default is ``None``, which estimates the noise from the median value of the 
  707         variance plane of ``maskedImage`` for each filter. 
  708     maxNumberOfPeaks: `int`, optional 
  709         If nonzero, the maximum number of peaks to deblend. 
  710         If the total number of peaks is greater than ``maxNumberOfPeaks``, 
  711         then only the first ``maxNumberOfPeaks`` sources are deblended. 
  715     debResult: `DeblendedParent` 
  716         Deblender result that contains a list of ``MultiColorPeak``s for each peak and 
  717         information that describes the footprint in all filters. 
  724         component = 
'meas_deblender.baseline' 
  725         log = lsstLog.Log.getLogger(component)
 
  728             log.setLevel(lsstLog.Log.TRACE)
 
  731     debResult = 
DeblenderResult(footprint, mMaskedImage, psfs, psfFwhms, log,
 
  732                                 maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
 
  735     while step < len(debPlugins):
 
  739         if not debResult.failed:
 
  740             reset = debPlugins[step].
run(debResult, log)
 
  742             log.warn(
"Skipping steps {0}".
format(debPlugins[step:]))
 
  745             step = debPlugins[step].onReset
 
  753     """Cache the PSF models 
  755     In the PSF fitting code, we request PSF models for all peaks near 
  756     the one being fit.  This was turning out to be quite expensive in 
  757     some cases.  Here, we cache the PSF models to bring the cost down 
  758     closer to O(N) rather than O(N^2). 
  766         im = self.
cache.get((cx, cy), 
None)
 
  773         self.
cache[(cx, cy)] = im