1 from builtins
import zip
2 from builtins
import str
3 from builtins
import range
4 from builtins
import object
27 from collections
import OrderedDict
49 """Collection of objects in multiple bands for a single parent footprint 52 def __init__(self, footprint, maskedImages, psfs, psffwhms, log, filters=None,
53 maxNumberOfPeaks=0, avgNoise=None):
54 """ Initialize a DeblededParent 58 footprint: `afw.detection.Footprint` 59 Parent footprint to deblend. While `maskedImages`, `psfs`, 60 and `psffwhms` are lists of objects, one for each band, 61 `footprint` is a single parent footprint (from a `mergedDet`) 62 this is used for all bands. 63 maskedImages: list of `afw.image.MaskedImageF`s 64 Masked image containing the ``footprint`` in each band. 65 psfs: list of `afw.detection.Psf`s 66 Psf of the ``maskedImage`` for each band. 67 psffwhm: list of `float`s 68 FWHM of the ``maskedImage``'s ``psf`` in each band. 69 filters: list of `string`, optional 70 Names of the filters. This should be the same length as the other lists 71 maxNumberOfPeaks: `int`, optional 72 If positive, the maximum number of peaks to deblend. 73 If the total number of peaks is greater than ``maxNumberOfPeaks``, 74 then only the first ``maxNumberOfPeaks`` sources are deblended. 75 The default is 0, which deblends all of the peaks. 76 avgNoise: `float`or list of `float`s, optional 77 Average noise level in each ``maskedImage``. 78 The default is ``None``, which estimates the noise from the median value of the 79 variance plane of ``maskedImage`` for each filter. 88 maskedImages = [maskedImages]
101 avgNoise = [
None]*len(psfs)
103 avgNoise = [avgNoise]
105 if any([len(maskedImages)!=len(p)
for p
in [psfs, psffwhms, avgNoise]]):
106 raise ValueError(
"To use the multi-color deblender, " 107 "'maskedImage', 'psf', 'psffwhm', 'avgNoise'" 108 "must have the same length, but instead have lengths: " 109 "{0}".format([len(p)
for p
in [maskedImages,
121 if maxNumberOfPeaks>0
and maxNumberOfPeaks<self.
peakCount:
133 psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
139 peakDict = OrderedDict([(f, dp.peaks[idx])
for f,dp
in self.
deblendedParents.items()])
141 self.
peaks.append(multiPeak)
148 """Get the footprint in each filter""" 153 self.templateSums[fidx] = templateSums
155 for f, templateSum
in templateSums.items():
159 """Deblender result of a single parent footprint, in a single band 161 Convenience class to store useful objects used by the deblender for a single band, 162 such as the maskedImage, psf, etc as well as the results from the deblender. 164 def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
165 maxNumberOfPeaks, debResult):
166 """Create a DeblendedParent to store a deblender result 171 Name of the filter used for `maskedImage` 172 footprint: list of `afw.detection.Footprint`s 173 Parent footprint to deblend in each band. 174 maskedImages: list of `afw.image.MaskedImageF`s 175 Masked image containing the ``footprint`` in each band. 176 psf: list of `afw.detection.Psf`s 177 Psf of the ``maskedImage`` for each band. 178 psffwhm: list of `float`s 179 FWHM of the ``maskedImage``'s ``psf`` in each band. 180 avgNoise: `float`or list of `float`s, optional 181 Average noise level in each ``maskedImage``. 182 The default is ``None``, which estimates the noise from the median value of the 183 variance plane of ``maskedImage`` for each filter. 184 maxNumberOfPeaks: `int`, optional 185 If positive, the maximum number of peaks to deblend. 186 If the total number of peaks is greater than ``maxNumberOfPeaks``, 187 then only the first ``maxNumberOfPeaks`` sources are deblended. 188 The default is 0, which deblends all of the peaks. 189 debResult: `DeblenderResult` 190 The ``DeblenderResult`` that contains this ``DeblendedParent``. 197 self.
img = maskedImage.getImage()
200 self.
mask = maskedImage.getMask()
209 stats = afwMath.makeStatistics(self.
varimg, self.
mask, afwMath.MEDIAN)
210 avgNoise = np.sqrt(stats.getValue(afwMath.MEDIAN))
211 debResult.log.trace(
'Estimated avgNoise for filter %s = %f', self.
filter, avgNoise)
216 peaks = self.
fp.getPeaks()
219 self.
peaks.append(deblendedPeak)
222 """Update the bounding box of the parent footprint 224 If the parent Footprint is resized it will be necessary to update the bounding box of the footprint. 228 if not self.
imbb.contains(self.
bb):
229 raise ValueError((
'Footprint bounding-box %s extends outside image bounding-box %s') %
230 (str(self.
bb), str(self.
imbb)))
231 self.W, self.
H = self.
bb.getWidth(), self.
bb.getHeight()
232 self.x0, self.
y0 = self.
bb.getMinX(), self.
bb.getMinY()
233 self.x1, self.
y1 = self.
bb.getMaxX(), self.
bb.getMaxY()
236 """Collection of single peak deblender results in multiple bands. 238 There is one of these objects for each Peak in the footprint. 242 """Create a collection for deblender results in each band. 246 peaks: `dict` of `afw.detection.PeakRecord` 247 Each entry in ``peaks`` is a peak record for the same peak in a different band 249 Index of the peak in `parent.peaks` 250 parent: `DeblendedParent` 251 DeblendedParent object that contains the peak as a child. 262 peak.multiColorPeak = self
274 """Result of deblending a single Peak within a parent Footprint. 276 There is one of these objects for each Peak in the Footprint. 279 def __init__(self, peak, pki, parent, multiColorPeak=None):
280 """Initialize a new deblended peak in a single filter band 284 peak: `afw.detection.PeakRecord` 285 Peak object in a single band from a peak record 287 Index of the peak in `multiColorPeak.parent.peaks` 288 parent: `DeblendedParent` 289 Parent in the same filter that contains the peak 290 multiColorPeak: `MultiColorPeak` 291 Object containing the same peak in multiple bands 369 return ((
'deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
384 Return a HeavyFootprint containing the flux apportioned to this peak. 386 @param[in] strayFlux include stray flux also? 393 heavy = afwDet.mergeHeavyFootprints(heavy, self.
strayFlux)
454 def deblend(footprint, maskedImage, psf, psffwhm, filters=None,
455 psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=True,
456 medianSmoothTemplate=True, medianFilterHalfsize=2,
457 monotonicTemplate=True, weightTemplates=False,
458 log=None, verbose=False, sigma1=None, maxNumberOfPeaks=0,
459 assignStrayFlux=True, strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak',
460 rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2,
461 getTemplateSum=False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=True,
462 removeDegenerateTemplates=False, maxTempDotProd=0.5
464 """Deblend a parent ``Footprint`` in a ``MaskedImageF``. 466 Deblending assumes that ``footprint`` has multiple peaks, as it will still create a 467 `PerFootprint` object with a list of peaks even if there is only one peak in the list. 468 It is recommended to first check that ``footprint`` has more than one peak, similar to the 469 execution of `lsst.meas.deblender.deblend.SourceDeblendTask`. 472 This is the API for the old deblender, however the function builds the plugins necessary 473 to use the new deblender to perform identically to the old deblender. 474 To test out newer functionality use ``newDeblend`` instead. 476 Deblending involves several mandatory and optional steps: 477 # Optional: If ``fitPsfs`` is True, find all peaks that are well-fit by a PSF + background model 478 * Peaks that pass the cuts have their footprints modified to the PSF + background model 479 and their ``deblendedAsPsf`` property set to ``True``. 480 * Relevant parameters: ``psfChisqCut1``, ``psfChisqCut2``, ``psfChisqCut2b``, 481 ``tinyFootprintSize``. 482 * See the parameter descriptions for more. 483 # Build a symmetric template for each peak not well-fit by the PSF model 484 * Given ``maskedImageF``, ``footprint``, and a ``DeblendedPeak``, creates a symmetric 485 template (``templateImage`` and ``templateFootprint``) around the peak 486 for all peaks not flagged as ``skip`` or ``deblendedAsPsf``. 487 * If ``patchEdges=True`` and if ``footprint`` touches pixels with the 488 ``EDGE`` bit set, then ``footprint`` is grown to include spans whose 489 symmetric mirror is outside of the image. 490 * Relevant parameters: ``sigma1`` and ``patchEdges``. 491 # Optional: If ``rampFluxAtEdge`` is True, adjust flux on the edges of the template footprints 492 * Using the PSF, a peak ``Footprint`` with pixels on the edge of of ``footprint`` 493 is grown by the psffwhm*1.5 and filled in with zeros. 494 * The result is a new symmetric footprint template for the peaks near the edge. 495 * Relevant parameter: ``patchEdges``. 496 # Optionally (``medianSmoothTemplate=True``) filter the template images 497 * Apply a median smoothing filter to all of the template images. 498 * Relevant parameters: ``medianFilterHalfSize`` 499 # Optional: If ``monotonicTemplate`` is True, make the templates monotonic. 500 * The pixels in the templates are modified such that pixels 501 further from the peak will have values smaller than those closer to the peak. 502 # Optional: If ``clipFootprintToNonzero`` is True, clip non-zero spans in the template footprints 503 * Peak ``Footprint``s are clipped to the region in the image containing non-zero values 504 by dropping spans that are completely zero and moving endpoints to non-zero pixels 505 (but does not split spans that have internal zeros). 506 # Optional: If ``weightTemplates`` is True, weight the templates to best fit the observed image 507 * Re-weight the templates so that their linear combination 508 best represents the observed ``maskedImage`` 509 # Optional: If ``removeDegenerateTempaltes`` is True, reconstruct shredded galaxies 510 * If galaxies have substructure, such as face-on spirals, the process of identifying peaks can 511 "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite 512 similar because they represent the same galaxy, so we try to identify these "degenerate" peaks 513 by looking at the inner product (in pixel space) of pairs of templates. 514 * If they are nearly parallel, we only keep one of the peaks and reject the other. 515 * If only one of the peaks is a PSF template, the other template is used, 516 otherwise the one with the maximum template value is kept. 517 * Relevant parameters: ``maxTempDotProduct`` 518 # Apportion flux to all of the peak templates 519 * Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of 520 flux assigned to each ``tempalteFootprint``. 521 * Leftover "stray flux" is assigned to peaks based on the other parameters. 522 * Relevant parameters: ``clipStrayFluxFraction``, ``strayFluxAssignment``, 523 ``strayFluxToPointSources``, ``assignStrayFlux`` 527 footprint: `afw.detection.Footprint` 528 Parent footprint to deblend 529 maskedImage: `afw.image.MaskedImageF` 530 Masked image containing the ``footprint`` 531 psf: `afw.detection.Psf` 532 Psf of the ``maskedImage`` 534 FWHM of the ``maskedImage``'s ``psf`` 535 psfChisqCut*: `float`, optional 536 If ``fitPsfs==True``, all of the peaks are fit to the image PSF. 537 ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to 538 be considered a PSF match without recentering. 539 A fit is also made that includes terms to recenter the PSF. 540 ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the 541 fit that includes recentering terms. 542 If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and 543 the peak footprint is fit again, this time to the new PSF. 544 If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it 545 passes the re-centering algorithm. 546 If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted, 547 but parameters for all three psf fits are stored in the ``DeblendedPeak``. 548 The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``. 549 fitPsfs: `bool`, optional 550 If True then all of the peaks will be compared to the image PSF to 551 distinguish stars from galaxies. 552 medianSmoothTemplate: ``bool``, optional 553 If ``medianSmoothTemplate==True`` it a median smoothing filter is applied to the ``maskedImage``. 554 The default is ``True``. 555 medianFilterHalfSize: `int`, optional 556 Half the box size of the median filter, ie a ``medianFilterHalfSize`` of 50 means that 557 each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image. 558 This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored. 559 The default value is 2. 560 monotonicTempalte: `bool`, optional 561 If True then make the template monotonic. 563 weightTemplates: `bool`, optional 564 If True, re-weight the templates so that their linear combination best represents 565 the observed ``maskedImage``. 566 The default is False. 567 log: `log.Log`, optional 568 LSST logger for logging purposes. 569 The default is ``None`` (no logging). 570 verbose: `bool`, optional 571 Whether or not to show a more verbose output. 572 The default is ``False``. 573 sigma1: `float`, optional 574 Average noise level in ``maskedImage``. 575 The default is ``None``, which estimates the noise from the median value of ``maskedImage``. 576 maxNumberOfPeaks: `int`, optional 577 If nonzero, the maximum number of peaks to deblend. 578 If the total number of peaks is greater than ``maxNumberOfPeaks``, 579 then only the first ``maxNumberOfPeaks`` sources are deblended. 580 The default is 0, which deblends all of the peaks. 581 assignStrayFlux: `bool`, optional 582 If True then flux in the parent footprint that is not covered by any of the 583 template footprints is assigned to templates based on their 1/(1+r^2) distance. 584 How the flux is apportioned is determined by ``strayFluxAssignment``. 586 strayFluxToPointSources: `string` 587 Determines how stray flux is apportioned to point sources 588 * ``never``: never apportion stray flux to point sources 589 * ``necessary`` (default): point sources are included only if there are no extended sources nearby 590 * ``always``: point sources are always included in the 1/(1+r^2) splitting 591 strayFluxAssignment: `string`, optional 592 Determines how stray flux is apportioned. 593 * ``trim``: Trim stray flux and do not include in any footprints 594 * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks 595 * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the 596 minimum distance from the stray flux to footprint 597 * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan) 598 distance to the stray flux 599 rampFluxAtEdge: `bool`, optional 600 If True then extend footprints with excessive flux on the edges as described above. 601 The default is False. 602 patchEdges: `bool`, optional 603 If True and if the footprint touches pixels with the ``EDGE`` bit set, 604 then grow the footprint to include all symmetric templates. 605 The default is ``False``. 606 tinyFootprintSize: `float`, optional 607 The PSF model is shrunk to the size that contains the original footprint. 608 If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)`` 609 then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit. 611 getTemplateSum: `bool`, optional 612 As part of the flux calculation, the sum of the templates is calculated. 613 If ``getTemplateSum==True`` then the sum of the templates is stored in the result (a `PerFootprint`). 614 The default is False. 615 clipStrayFluxFraction: `float`, optional 616 Minimum stray-flux portion. 617 Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero. 618 The default is 0.001. 619 clipFootprintToNonzero: `bool`, optional 620 If True then clip non-zero spans in the template footprints. See above for more. 622 removeDegenerateTemplates: `bool`, optional 623 If True then we try to identify "degenerate" peaks by looking at the inner product 624 (in pixel space) of pairs of templates. 625 The default is False. 626 maxTempDotProduct: `float`, optional 627 All dot products between templates greater than ``maxTempDotProduct`` will result in one 628 of the templates removed. This parameter is only used when ``removeDegenerateTempaltes==True``. 634 Deblender result that contains a list of ``DeblendedPeak``s for each peak and (optionally) 644 psfChisqCut1=psfChisqCut1,
645 psfChisqCut2=psfChisqCut2,
646 psfChisqCut2b=psfChisqCut2b,
647 tinyFootprintSize=tinyFootprintSize))
651 if medianSmoothTemplate:
653 medianFilterHalfsize=medianFilterHalfsize))
654 if monotonicTemplate:
656 if clipFootprintToNonzero:
660 if removeDegenerateTemplates:
662 onReset = len(debPlugins)-1
664 onReset = len(debPlugins)
667 maxTempDotProd=maxTempDotProd))
669 clipStrayFluxFraction=clipStrayFluxFraction,
670 assignStrayFlux=assignStrayFlux,
671 strayFluxAssignment=strayFluxAssignment,
672 strayFluxToPointSources=strayFluxToPointSources,
673 getTemplateSum=getTemplateSum))
675 debResult =
newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters, log, verbose, avgNoise)
679 def newDeblend(debPlugins, footprint, maskedImages, psfs, psfFwhms, filters=None,
680 log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0):
681 """Deblend a parent ``Footprint`` in a ``MaskedImageF``. 683 Deblending assumes that ``footprint`` has multiple peaks, as it will still create a 684 `PerFootprint` object with a list of peaks even if there is only one peak in the list. 685 It is recommended to first check that ``footprint`` has more than one peak, similar to the 686 execution of `lsst.meas.deblender.deblend.SourceDeblendTask`. 688 This version of the deblender accepts a list of plugins to execute, with the option to re-run parts 689 of the deblender if templates are changed during any of the steps. 693 debPlugins: list of `meas.deblender.plugins.DeblenderPlugins` 694 Plugins to execute (in order of execution) for the deblender. 695 footprint: `afw.detection.Footprint` or list of Footprints 696 Parent footprint to deblend. 697 maskedImages: `afw.image.MaskedImageF` or list of MaskedImages 698 Masked image containing the ``footprint``. 699 psfs: `afw.detection.Psf` or list of Psfs 700 Psf of the ``maskedImage``. 701 psfFwhms: `float` or list of floats 702 FWHM of the ``maskedImage``'s ``psf``. 703 filters: list of `string`s, optional 704 Names of the filters when ``footprint`` is a list instead of a single ``footprint``. 705 This is used to index the deblender results by filter. 706 The default is None, which will numerically index lists of objects. 707 log: `log.Log`, optional 708 LSST logger for logging purposes. 709 The default is ``None`` (no logging). 710 verbose: `bool`, optional 711 Whether or not to show a more verbose output. 712 The default is ``False``. 713 avgNoise: `float`or list of `float`s, optional 714 Average noise level in each ``maskedImage``. 715 The default is ``None``, which estimates the noise from the median value of the 716 variance plane of ``maskedImage`` for each filter. 717 maxNumberOfPeaks: `int`, optional 718 If nonzero, the maximum number of peaks to deblend. 719 If the total number of peaks is greater than ``maxNumberOfPeaks``, 720 then only the first ``maxNumberOfPeaks`` sources are deblended. 724 debResult: `DeblendedParent` 725 Deblender result that contains a list of ``MultiColorPeak``s for each peak and 726 information that describes the footprint in all filters. 730 butils = deb.BaselineUtilsF
735 component =
'meas_deblender.baseline' 736 log = lsstLog.Log.getLogger(component)
739 log.setLevel(lsstLog.Log.TRACE)
742 debResult =
DeblenderResult(footprint, maskedImages, psfs, psfFwhms, log, filters=filters,
743 maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
746 while step < len(debPlugins):
750 if not debResult.failed:
751 reset = debPlugins[step].run(debResult, log)
753 log.warn(
"Skipping steps {0}".format(debPlugins[step:]))
756 step = debPlugins[step].onReset
764 """Cache the PSF models 766 In the PSF fitting code, we request PSF models for all peaks near 767 the one being fit. This was turning out to be quite expensive in 768 some cases. Here, we cache the PSF models to bring the cost down 769 closer to O(N) rather than O(N^2). 777 im = self.
cache.get((cx, cy),
None)
784 self.
cache[(cx, cy)] = im
def setDeblendedAsPsf(self)
def setOrigTemplate(self, t, tfoot)
def setTemplate(self, image, footprint)
def setFailedSymmetricTemplate(self)
def setPsfFitFailed(self)
def setStrayFlux(self, stray)
def setNoValidPixels(self)
def setFluxPortion(self, mimg)
def setPsfTemplate(self, tim, tfoot)
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)
def setMedianFilteredTemplate(self, t, tfoot)
def __init__(self, peak, pki, parent, multiColorPeak=None)
def updateFootprintBbox(self)
def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise, maxNumberOfPeaks, debResult)
def computeImage(self, cx, cy)
def __init__(self, peaks, pki, parent)
def __init__(self, footprint, maskedImages, psfs, psffwhms, log, filters=None, maxNumberOfPeaks=0, avgNoise=None)
def setTemplateSums(self, templateSums, fidx=None)
def setTemplateWeight(self, w)
def setTinyFootprint(self)
def newDeblend(debPlugins, footprint, maskedImages, psfs, psfFwhms, filters=None, log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0)
def getFluxPortion(self, strayFlux=True)
Return a HeavyFootprint containing the flux apportioned to this peak.
def setRampedTemplate(self, t, tfoot)
def getParentProperty(self, propertyName)