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, footprints, maskedImages, psfs, psffwhms, log, filters=None,
53 maxNumberOfPeaks=0, avgNoise=None):
54 """ Initialize a DeblededParent 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. 85 footprints = [footprints]
89 maskedImages = [maskedImages]
102 avgNoise = [
None]*len(psfs)
104 avgNoise = [avgNoise]
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,
120 if maxNumberOfPeaks>0
and maxNumberOfPeaks<self.
peakCount:
124 filters = range(len(footprints))
132 psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
140 self.
peaks.append(multiPeak)
143 """Get the footprint in each filter""" 148 self.templateSums[fidx] = templateSums
150 for f, templateSum
in templateSums.items():
154 """Deblender result of a single parent footprint, in a single band 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. 159 def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
160 maxNumberOfPeaks, debResult):
161 """Create a DeblendedParent to store a deblender result 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``. 192 self.
img = maskedImage.getImage()
195 self.
mask = maskedImage.getMask()
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)
211 peaks = self.
fp.getPeaks()
214 self.
peaks.append(deblendedPeak)
217 """Update the bounding box of the parent footprint 219 If the parent Footprint is resized it will be necessary to update the bounding box of the footprint. 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()
231 """Collection of single peak deblender results in multiple bands. 233 There is one of these objects for each Peak in the footprint. 237 """Create a collection for deblender results in each band. 241 peaks: `dict` of `afw.detection.PeakRecord` 242 Each entry in ``peaks`` is a peak record for the same peak in a different band 244 Index of the peak in `parent.peaks` 245 parent: `DeblendedParent` 246 DeblendedParent object that contains the peak as a child. 257 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? 386 heavy = afwDet.mergeHeavyFootprints(heavy, self.
strayFlux)
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
457 """Deblend a parent ``Footprint`` in a ``MaskedImageF``. 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`. 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. 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`` 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`` 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. 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``. 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. 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. 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``. 627 Deblender result that contains a list of ``DeblendedPeak``s for each peak and (optionally) 637 psfChisqCut1=psfChisqCut1,
638 psfChisqCut2=psfChisqCut2,
639 psfChisqCut2b=psfChisqCut2b,
640 tinyFootprintSize=tinyFootprintSize))
644 if medianSmoothTemplate:
646 medianFilterHalfsize=medianFilterHalfsize))
647 if monotonicTemplate:
649 if clipFootprintToNonzero:
653 if removeDegenerateTemplates:
655 onReset = len(debPlugins)-1
657 onReset = len(debPlugins)
660 maxTempDotProd=maxTempDotProd))
662 clipStrayFluxFraction=clipStrayFluxFraction,
663 assignStrayFlux=assignStrayFlux,
664 strayFluxAssignment=strayFluxAssignment,
665 strayFluxToPointSources=strayFluxToPointSources,
666 getTemplateSum=getTemplateSum))
668 debResult =
newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters, log, verbose, avgNoise)
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``. 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`. 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. 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. 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. 723 butils = deb.BaselineUtilsF
728 component =
'meas_deblender.baseline' 729 log = lsstLog.Log.getLogger(component)
732 log.setLevel(lsstLog.Log.TRACE)
735 debResult =
DeblenderResult(footprint, maskedImage, psf, psffwhm, log, filters=filters,
736 maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
739 while step < len(debPlugins):
740 reset = debPlugins[step].run(debResult, log)
742 step = debPlugins[step].onReset
750 """Cache the PSF models 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). 763 im = self.
cache.get((cx, cy),
None)
770 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 newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters=None, log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0)
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 setTemplateSums(self, templateSums, fidx=None)
def setTemplateWeight(self, w)
def setTinyFootprint(self)
def getFluxPortion(self, strayFlux=True)
Return a HeavyFootprint containing the flux apportioned to this peak.
def setRampedTemplate(self, t, tfoot)
def getParentProperty(self, propertyName)
def __init__(self, footprints, maskedImages, psfs, psffwhms, log, filters=None, maxNumberOfPeaks=0, avgNoise=None)