LSSTApplications  20.0.0
LSSTDataManagementBasePackage
Classes | Functions
lsst.meas.deblender.plugins Namespace Reference

Classes

class  DeblenderPlugin
 

Functions

def clipFootprintToNonzeroImpl (foot, image)
 
def fitPsfs (debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2)
 
def buildSymmetricTemplates (debResult, log, patchEdges=False, setOrigTemplate=True)
 
def rampFluxAtEdge (debResult, log, patchEdges=False)
 
def medianSmoothTemplates (debResult, log, medianFilterHalfsize=2)
 
def makeTemplatesMonotonic (debResult, log)
 
def clipFootprintsToNonzero (debResult, log)
 
def weightTemplates (debResult, log)
 
def reconstructTemplates (debResult, log, maxTempDotProd=0.5)
 
def apportionFlux (debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak', strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001, getTemplateSum=False)
 

Function Documentation

◆ apportionFlux()

def lsst.meas.deblender.plugins.apportionFlux (   debResult,
  log,
  assignStrayFlux = True,
  strayFluxAssignment = 'r-to-peak',
  strayFluxToPointSources = 'necessary',
  clipStrayFluxFraction = 0.001,
  getTemplateSum = False 
)
Apportion flux to all of the peak templates in each filter

Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of
flux assigned to each ``template``.
Leftover "stray flux" is assigned to peaks based on the other parameters.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
assignStrayFlux: `bool`, optional
    If True then flux in the parent footprint that is not covered by any of the
    template footprints is assigned to templates based on their 1/(1+r^2) distance.
    How the flux is apportioned is determined by ``strayFluxAssignment``.
strayFluxAssignment: `string`, optional
    Determines how stray flux is apportioned.
    * ``trim``: Trim stray flux and do not include in any footprints
    * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks
    * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the
      minimum distance from the stray flux to footprint
    * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan)
      distance to the stray flux
strayFluxToPointSources: `string`, optional
    Determines how stray flux is apportioned to point sources
    * ``never``: never apportion stray flux to point sources
    * ``necessary`` (default): point sources are included only if there are no extended sources nearby
    * ``always``: point sources are always included in the 1/(1+r^2) splitting
clipStrayFluxFraction: `float`, optional
    Minimum stray-flux portion.
    Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero.
getTemplateSum: `bool`, optional
    As part of the flux calculation, the sum of the templates is calculated.
    If ``getTemplateSum==True`` then the sum of the templates is stored in the result
    (a `DeblendedFootprint`).

Returns
-------
modified: `bool`
    Apportion flux always modifies the templates, so ``modified`` is always ``True``.
    However, this should likely be the final step and it is unlikely that
    any deblender plugins will be re-run.

Definition at line 1216 of file plugins.py.

1216 def apportionFlux(debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak',
1217  strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001,
1218  getTemplateSum=False):
1219  """Apportion flux to all of the peak templates in each filter
1220 
1221  Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of
1222  flux assigned to each ``template``.
1223  Leftover "stray flux" is assigned to peaks based on the other parameters.
1224 
1225  Parameters
1226  ----------
1227  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1228  Container for the final deblender results.
1229  log: `log.Log`
1230  LSST logger for logging purposes.
1231  assignStrayFlux: `bool`, optional
1232  If True then flux in the parent footprint that is not covered by any of the
1233  template footprints is assigned to templates based on their 1/(1+r^2) distance.
1234  How the flux is apportioned is determined by ``strayFluxAssignment``.
1235  strayFluxAssignment: `string`, optional
1236  Determines how stray flux is apportioned.
1237  * ``trim``: Trim stray flux and do not include in any footprints
1238  * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks
1239  * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the
1240  minimum distance from the stray flux to footprint
1241  * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan)
1242  distance to the stray flux
1243  strayFluxToPointSources: `string`, optional
1244  Determines how stray flux is apportioned to point sources
1245  * ``never``: never apportion stray flux to point sources
1246  * ``necessary`` (default): point sources are included only if there are no extended sources nearby
1247  * ``always``: point sources are always included in the 1/(1+r^2) splitting
1248  clipStrayFluxFraction: `float`, optional
1249  Minimum stray-flux portion.
1250  Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero.
1251  getTemplateSum: `bool`, optional
1252  As part of the flux calculation, the sum of the templates is calculated.
1253  If ``getTemplateSum==True`` then the sum of the templates is stored in the result
1254  (a `DeblendedFootprint`).
1255 
1256  Returns
1257  -------
1258  modified: `bool`
1259  Apportion flux always modifies the templates, so ``modified`` is always ``True``.
1260  However, this should likely be the final step and it is unlikely that
1261  any deblender plugins will be re-run.
1262  """
1263  validStrayPtSrc = ['never', 'necessary', 'always']
1264  validStrayAssign = ['r-to-peak', 'r-to-footprint', 'nearest-footprint', 'trim']
1265  if strayFluxToPointSources not in validStrayPtSrc:
1266  raise ValueError((('strayFluxToPointSources: value \"%s\" not in the set of allowed values: ') %
1267  strayFluxToPointSources) + str(validStrayPtSrc))
1268  if strayFluxAssignment not in validStrayAssign:
1269  raise ValueError((('strayFluxAssignment: value \"%s\" not in the set of allowed values: ') %
1270  strayFluxAssignment) + str(validStrayAssign))
1271 
1272  for fidx in debResult.filters:
1273  dp = debResult.deblendedParents[fidx]
1274  # Prepare inputs to "apportionFlux" call.
1275  # template maskedImages
1276  tmimgs = []
1277  # template footprints
1278  tfoots = []
1279  # deblended as psf
1280  dpsf = []
1281  # peak x,y
1282  pkx = []
1283  pky = []
1284  # indices of valid templates
1285  ibi = []
1286  bb = dp.fp.getBBox()
1287 
1288  for peaki, pkres in enumerate(dp.peaks):
1289  if pkres.skip:
1290  continue
1291  tmimgs.append(pkres.templateImage)
1292  tfoots.append(pkres.templateFootprint)
1293  # for stray flux...
1294  dpsf.append(pkres.deblendedAsPsf)
1295  pk = pkres.peak
1296  pkx.append(pk.getIx())
1297  pky.append(pk.getIy())
1298  ibi.append(pkres.pki)
1299 
1300  # Now apportion flux according to the templates
1301  log.trace('Apportioning flux among %i templates', len(tmimgs))
1302  sumimg = afwImage.ImageF(bb)
1303  # .getDimensions())
1304  # sumimg.setXY0(bb.getMinX(), bb.getMinY())
1305 
1306  strayopts = 0
1307  if strayFluxAssignment == 'trim':
1308  assignStrayFlux = False
1309  strayopts |= bUtils.STRAYFLUX_TRIM
1310  if assignStrayFlux:
1311  strayopts |= bUtils.ASSIGN_STRAYFLUX
1312  if strayFluxToPointSources == 'necessary':
1313  strayopts |= bUtils.STRAYFLUX_TO_POINT_SOURCES_WHEN_NECESSARY
1314  elif strayFluxToPointSources == 'always':
1315  strayopts |= bUtils.STRAYFLUX_TO_POINT_SOURCES_ALWAYS
1316 
1317  if strayFluxAssignment == 'r-to-peak':
1318  # this is the default
1319  pass
1320  elif strayFluxAssignment == 'r-to-footprint':
1321  strayopts |= bUtils.STRAYFLUX_R_TO_FOOTPRINT
1322  elif strayFluxAssignment == 'nearest-footprint':
1323  strayopts |= bUtils.STRAYFLUX_NEAREST_FOOTPRINT
1324 
1325  portions, strayflux = bUtils.apportionFlux(dp.maskedImage, dp.fp, tmimgs, tfoots, sumimg, dpsf,
1326  pkx, pky, strayopts, clipStrayFluxFraction)
1327 
1328  # Shrink parent to union of children
1329  if strayFluxAssignment == 'trim':
1330  finalSpanSet = afwGeom.SpanSet()
1331  for foot in tfoots:
1332  finalSpanSet = finalSpanSet.union(foot.spans)
1333  dp.fp.setSpans(finalSpanSet)
1334 
1335  # Store the template sum in the deblender result
1336  if getTemplateSum:
1337  debResult.setTemplateSums(sumimg, fidx)
1338 
1339  # Save the apportioned fluxes
1340  ii = 0
1341  for j, (pk, pkres) in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1342  if pkres.skip:
1343  continue
1344  pkres.setFluxPortion(portions[ii])
1345 
1346  if assignStrayFlux:
1347  # NOTE that due to a swig bug (https://github.com/swig/swig/issues/59)
1348  # we CANNOT iterate over "strayflux", but must index into it.
1349  stray = strayflux[ii]
1350  else:
1351  stray = None
1352  ii += 1
1353 
1354  pkres.setStrayFlux(stray)
1355 
1356  # Set child footprints to contain the right number of peaks.
1357  for j, (pk, pkres) in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1358  if pkres.skip:
1359  continue
1360 
1361  for foot, add in [(pkres.templateFootprint, True), (pkres.origFootprint, True),
1362  (pkres.strayFlux, False)]:
1363  if foot is None:
1364  continue
1365  pks = foot.getPeaks()
1366  pks.clear()
1367  if add:
1368  pks.append(pk)
1369  return True

◆ buildSymmetricTemplates()

def lsst.meas.deblender.plugins.buildSymmetricTemplates (   debResult,
  log,
  patchEdges = False,
  setOrigTemplate = True 
)
Build a symmetric template for each peak in each filter

Given ``maskedImageF``, ``footprint``, and a ``DebldendedPeak``, creates a symmetric template
(``templateImage`` and ``templateFootprint``) around the peak for all peaks not flagged as
``skip`` or ``deblendedAsPsf``.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
patchEdges: `bool`, optional
    If True and if the parent Footprint touches pixels with the ``EDGE`` bit set,
    then grow the parent Footprint to include all symmetric templates.

Returns
-------
modified: `bool`
    If any peaks are not skipped or marked as point sources, ``modified`` is ``True.
    Otherwise ``modified`` is ``False``.

Definition at line 689 of file plugins.py.

689 def buildSymmetricTemplates(debResult, log, patchEdges=False, setOrigTemplate=True):
690  """Build a symmetric template for each peak in each filter
691 
692  Given ``maskedImageF``, ``footprint``, and a ``DebldendedPeak``, creates a symmetric template
693  (``templateImage`` and ``templateFootprint``) around the peak for all peaks not flagged as
694  ``skip`` or ``deblendedAsPsf``.
695 
696  Parameters
697  ----------
698  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
699  Container for the final deblender results.
700  log: `log.Log`
701  LSST logger for logging purposes.
702  patchEdges: `bool`, optional
703  If True and if the parent Footprint touches pixels with the ``EDGE`` bit set,
704  then grow the parent Footprint to include all symmetric templates.
705 
706  Returns
707  -------
708  modified: `bool`
709  If any peaks are not skipped or marked as point sources, ``modified`` is ``True.
710  Otherwise ``modified`` is ``False``.
711  """
712  modified = False
713  # Create the Templates for each peak in each filter
714  for fidx in debResult.filters:
715  dp = debResult.deblendedParents[fidx]
716  imbb = dp.img.getBBox()
717  log.trace('Creating templates for footprint at x0,y0,W,H = %i, %i, %i, %i)', dp.x0, dp.y0, dp.W, dp.H)
718 
719  for peaki, pkres in enumerate(dp.peaks):
720  log.trace('Deblending peak %i of %i', peaki, len(dp.peaks))
721  # TODO: Check debResult to see if the peak is deblended as a point source
722  # when comparing all bands, not just a single band
723  if pkres.skip or pkres.deblendedAsPsf:
724  continue
725  modified = True
726  pk = pkres.peak
727  cx, cy = pk.getIx(), pk.getIy()
728  if not imbb.contains(geom.Point2I(cx, cy)):
729  log.trace('Peak center is not inside image; skipping %i', pkres.pki)
730  pkres.setOutOfBounds()
731  continue
732  log.trace('computing template for peak %i at (%i, %i)', pkres.pki, cx, cy)
733  timg, tfoot, patched = bUtils.buildSymmetricTemplate(dp.maskedImage, dp.fp, pk, dp.avgNoise,
734  True, patchEdges)
735  if timg is None:
736  log.trace('Peak %i at (%i, %i): failed to build symmetric template', pkres.pki, cx, cy)
737  pkres.setFailedSymmetricTemplate()
738  continue
739 
740  if patched:
741  pkres.setPatched()
742 
743  # possibly save the original symmetric template
744  if setOrigTemplate:
745  pkres.setOrigTemplate(timg, tfoot)
746  pkres.setTemplate(timg, tfoot)
747  return modified
748 
749 

◆ clipFootprintsToNonzero()

def lsst.meas.deblender.plugins.clipFootprintsToNonzero (   debResult,
  log 
)
Clip non-zero spans in the template footprints for every peak in each filter.

Peak ``Footprint``s are clipped to the region in the image containing non-zero values
by dropping spans that are completely zero and moving endpoints to non-zero pixels
(but does not split spans that have internal zeros).

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.

Returns
-------
modified: `bool`
    Whether or not any templates were modified.
    This will be ``True`` as long as there is at least one source that is not flagged as a PSF.

Definition at line 1013 of file plugins.py.

1013 def clipFootprintsToNonzero(debResult, log):
1014  """Clip non-zero spans in the template footprints for every peak in each filter.
1015 
1016  Peak ``Footprint``s are clipped to the region in the image containing non-zero values
1017  by dropping spans that are completely zero and moving endpoints to non-zero pixels
1018  (but does not split spans that have internal zeros).
1019 
1020  Parameters
1021  ----------
1022  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1023  Container for the final deblender results.
1024  log: `log.Log`
1025  LSST logger for logging purposes.
1026 
1027  Returns
1028  -------
1029  modified: `bool`
1030  Whether or not any templates were modified.
1031  This will be ``True`` as long as there is at least one source that is not flagged as a PSF.
1032  """
1033  # Loop over all filters
1034  for fidx in debResult.filters:
1035  dp = debResult.deblendedParents[fidx]
1036  for peaki, pkres in enumerate(dp.peaks):
1037  if pkres.skip or pkres.deblendedAsPsf:
1038  continue
1039  timg, tfoot = pkres.templateImage, pkres.templateFootprint
1040  clipFootprintToNonzeroImpl(tfoot, timg)
1041  if not tfoot.getBBox().isEmpty() and tfoot.getBBox() != timg.getBBox(afwImage.PARENT):
1042  timg = timg.Factory(timg, tfoot.getBBox(), afwImage.PARENT, True)
1043  pkres.setTemplate(timg, tfoot)
1044  return False
1045 
1046 

◆ clipFootprintToNonzeroImpl()

def lsst.meas.deblender.plugins.clipFootprintToNonzeroImpl (   foot,
  image 
)
 Clips the given *Footprint* to the region in the *Image*
 containing non-zero values.  The clipping drops spans that are
 totally zero, and moves endpoints to non-zero; it does not
 split spans that have internal zeros.

Definition at line 34 of file plugins.py.

34 def clipFootprintToNonzeroImpl(foot, image):
35  '''
36  Clips the given *Footprint* to the region in the *Image*
37  containing non-zero values. The clipping drops spans that are
38  totally zero, and moves endpoints to non-zero; it does not
39  split spans that have internal zeros.
40  '''
41  x0 = image.getX0()
42  y0 = image.getY0()
43  xImMax = x0 + image.getDimensions().getX()
44  yImMax = y0 + image.getDimensions().getY()
45  newSpans = []
46  arr = image.getArray()
47  for span in foot.spans:
48  y = span.getY()
49  if y < y0 or y > yImMax:
50  continue
51  spanX0 = span.getX0()
52  spanX1 = span.getX1()
53  xMin = spanX0 if spanX0 >= x0 else x0
54  xMax = spanX1 if spanX1 <= xImMax else xImMax
55  xarray = np.arange(xMin, xMax+1)[arr[y-y0, xMin-x0:xMax-x0+1] != 0]
56  if len(xarray) > 0:
57  newSpans.append(afwGeom.Span(y, xarray[0], xarray[-1]))
58  # Time to update the SpanSet
59  foot.setSpans(afwGeom.SpanSet(newSpans, normalize=False))
60  foot.removeOrphanPeaks()
61 
62 

◆ fitPsfs()

def lsst.meas.deblender.plugins.fitPsfs (   debResult,
  log,
  psfChisqCut1 = 1.5,
  psfChisqCut2 = 1.5,
  psfChisqCut2b = 1.5,
  tinyFootprintSize = 2 
)
Fit a PSF + smooth background model (linear) to a small region around each peak

This function will iterate over all filters in deblender result but does not compare
results across filters.
DeblendedPeaks that pass the cuts have their templates modified to the PSF + background model
and their ``deblendedAsPsf`` property set to ``True``.

This will likely be replaced in the future with a function that compares the psf chi-squared cuts
so that peaks flagged as point sources will be considered point sources in all bands.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
psfChisqCut*: `float`, optional
    ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to
    be considered a PSF match without recentering.
    A fit is also made that includes terms to recenter the PSF.
    ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the
    fit that includes recentering terms.
    If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and
    the peak footprint is fit again, this time to the new PSF.
    If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it
    passes the re-centering algorithm.
    If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted,
    but parameters for all three psf fits are stored in the ``DebldendedPeak``.
    The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``.
tinyFootprintSize: `float`, optional
    The PSF model is shrunk to the size that contains the original footprint.
    If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)``
    then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit.
    The default is 2.

Returns
-------
modified: `bool`
    If any templates have been assigned to PSF point sources then ``modified`` is ``True``,
    otherwise it is ``False``.

Definition at line 154 of file plugins.py.

154 def fitPsfs(debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2):
155  """Fit a PSF + smooth background model (linear) to a small region around each peak
156 
157  This function will iterate over all filters in deblender result but does not compare
158  results across filters.
159  DeblendedPeaks that pass the cuts have their templates modified to the PSF + background model
160  and their ``deblendedAsPsf`` property set to ``True``.
161 
162  This will likely be replaced in the future with a function that compares the psf chi-squared cuts
163  so that peaks flagged as point sources will be considered point sources in all bands.
164 
165  Parameters
166  ----------
167  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
168  Container for the final deblender results.
169  log: `log.Log`
170  LSST logger for logging purposes.
171  psfChisqCut*: `float`, optional
172  ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to
173  be considered a PSF match without recentering.
174  A fit is also made that includes terms to recenter the PSF.
175  ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the
176  fit that includes recentering terms.
177  If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and
178  the peak footprint is fit again, this time to the new PSF.
179  If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it
180  passes the re-centering algorithm.
181  If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted,
182  but parameters for all three psf fits are stored in the ``DebldendedPeak``.
183  The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``.
184  tinyFootprintSize: `float`, optional
185  The PSF model is shrunk to the size that contains the original footprint.
186  If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)``
187  then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit.
188  The default is 2.
189 
190  Returns
191  -------
192  modified: `bool`
193  If any templates have been assigned to PSF point sources then ``modified`` is ``True``,
194  otherwise it is ``False``.
195  """
196  from .baseline import CachingPsf
197  modified = False
198  # Loop over all of the filters to build the PSF
199  for fidx in debResult.filters:
200  dp = debResult.deblendedParents[fidx]
201  peaks = dp.fp.getPeaks()
202  cpsf = CachingPsf(dp.psf)
203 
204  # create mask image for pixels within the footprint
205  fmask = afwImage.Mask(dp.bb)
206  fmask.setXY0(dp.bb.getMinX(), dp.bb.getMinY())
207  dp.fp.spans.setMask(fmask, 1)
208 
209  # pk.getF() -- retrieving the floating-point location of the peak
210  # -- actually shows up in the profile if we do it in the loop, so
211  # grab them all here.
212  peakF = [pk.getF() for pk in peaks]
213 
214  for pki, (pk, pkres, pkF) in enumerate(zip(peaks, dp.peaks, peakF)):
215  log.trace('Filter %s, Peak %i', fidx, pki)
216  ispsf = _fitPsf(dp.fp, fmask, pk, pkF, pkres, dp.bb, peaks, peakF, log, cpsf, dp.psffwhm,
217  dp.img, dp.varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b, tinyFootprintSize)
218  modified = modified or ispsf
219  return modified
220 
221 

◆ makeTemplatesMonotonic()

def lsst.meas.deblender.plugins.makeTemplatesMonotonic (   debResult,
  log 
)
Make the templates monotonic.

The pixels in the templates are modified such that pixels further from the peak will
have values smaller than those closer to the peak.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.

Returns
-------
modified: `bool`
    Whether or not any templates were modified.
    This will be ``True`` as long as there is at least one source that is not flagged as a PSF.

Definition at line 978 of file plugins.py.

978 def makeTemplatesMonotonic(debResult, log):
979  """Make the templates monotonic.
980 
981  The pixels in the templates are modified such that pixels further from the peak will
982  have values smaller than those closer to the peak.
983 
984  Parameters
985  ----------
986  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
987  Container for the final deblender results.
988  log: `log.Log`
989  LSST logger for logging purposes.
990 
991  Returns
992  -------
993  modified: `bool`
994  Whether or not any templates were modified.
995  This will be ``True`` as long as there is at least one source that is not flagged as a PSF.
996  """
997  modified = False
998  # Loop over all filters
999  for fidx in debResult.filters:
1000  dp = debResult.deblendedParents[fidx]
1001  for peaki, pkres in enumerate(dp.peaks):
1002  if pkres.skip or pkres.deblendedAsPsf:
1003  continue
1004  modified = True
1005  timg, tfoot = pkres.templateImage, pkres.templateFootprint
1006  pk = pkres.peak
1007  log.trace('Making template %i monotonic', pkres.pki)
1008  bUtils.makeMonotonic(timg, pk)
1009  pkres.setTemplate(timg, tfoot)
1010  return modified
1011 
1012 

◆ medianSmoothTemplates()

def lsst.meas.deblender.plugins.medianSmoothTemplates (   debResult,
  log,
  medianFilterHalfsize = 2 
)
Applying median smoothing filter to the template images for every peak in every filter.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
medianFilterHalfSize: `int`, optional
    Half the box size of the median filter, i.e. a ``medianFilterHalfSize`` of 50 means that
    each output pixel will be the median of  the pixels in a 101 x 101-pixel box in the input image.
    This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored.

Returns
-------
modified: `bool`
    Whether or not any templates were modified.
    This will be ``True`` as long as there is at least one source that is not flagged as a PSF.

Definition at line 933 of file plugins.py.

933 def medianSmoothTemplates(debResult, log, medianFilterHalfsize=2):
934  """Applying median smoothing filter to the template images for every peak in every filter.
935 
936  Parameters
937  ----------
938  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
939  Container for the final deblender results.
940  log: `log.Log`
941  LSST logger for logging purposes.
942  medianFilterHalfSize: `int`, optional
943  Half the box size of the median filter, i.e. a ``medianFilterHalfSize`` of 50 means that
944  each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image.
945  This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored.
946 
947  Returns
948  -------
949  modified: `bool`
950  Whether or not any templates were modified.
951  This will be ``True`` as long as there is at least one source that is not flagged as a PSF.
952  """
953  modified = False
954  # Loop over all filters
955  for fidx in debResult.filters:
956  dp = debResult.deblendedParents[fidx]
957  for peaki, pkres in enumerate(dp.peaks):
958  if pkres.skip or pkres.deblendedAsPsf:
959  continue
960  modified = True
961  timg, tfoot = pkres.templateImage, pkres.templateFootprint
962  filtsize = medianFilterHalfsize*2 + 1
963  if timg.getWidth() >= filtsize and timg.getHeight() >= filtsize:
964  log.trace('Median filtering template %i', pkres.pki)
965  # We want the output to go in "t1", so copy it into
966  # "inimg" for input
967  inimg = timg.Factory(timg, True)
968  bUtils.medianFilter(inimg, timg, medianFilterHalfsize)
969  # possible save this median-filtered template
970  pkres.setMedianFilteredTemplate(timg, tfoot)
971  else:
972  log.trace('Not median-filtering template %i: size %i x %i smaller than required %i x %i',
973  pkres.pki, timg.getWidth(), timg.getHeight(), filtsize, filtsize)
974  pkres.setTemplate(timg, tfoot)
975  return modified
976 
977 

◆ rampFluxAtEdge()

def lsst.meas.deblender.plugins.rampFluxAtEdge (   debResult,
  log,
  patchEdges = False 
)
Adjust flux on the edges of the template footprints.

Using the PSF, a peak ``Footprint`` with pixels on the edge of ``footprint``
is grown by the ``psffwhm``*1.5 and filled in with ramped pixels.
The result is a new symmetric footprint template for the peaks near the edge.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
patchEdges: `bool`, optional
    If True and if the parent Footprint touches pixels with the ``EDGE`` bit set,
    then grow the parent Footprint to include all symmetric templates.

Returns
-------
modified: `bool`
    If any peaks have their templates modified to include flux at the edges,
    ``modified`` is ``True``.

Definition at line 750 of file plugins.py.

750 def rampFluxAtEdge(debResult, log, patchEdges=False):
751  """Adjust flux on the edges of the template footprints.
752 
753  Using the PSF, a peak ``Footprint`` with pixels on the edge of ``footprint``
754  is grown by the ``psffwhm``*1.5 and filled in with ramped pixels.
755  The result is a new symmetric footprint template for the peaks near the edge.
756 
757  Parameters
758  ----------
759  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
760  Container for the final deblender results.
761  log: `log.Log`
762  LSST logger for logging purposes.
763  patchEdges: `bool`, optional
764  If True and if the parent Footprint touches pixels with the ``EDGE`` bit set,
765  then grow the parent Footprint to include all symmetric templates.
766 
767  Returns
768  -------
769  modified: `bool`
770  If any peaks have their templates modified to include flux at the edges,
771  ``modified`` is ``True``.
772  """
773  modified = False
774  # Loop over all filters
775  for fidx in debResult.filters:
776  dp = debResult.deblendedParents[fidx]
777  log.trace('Checking for significant flux at edge: sigma1=%g', dp.avgNoise)
778 
779  for peaki, pkres in enumerate(dp.peaks):
780  if pkres.skip or pkres.deblendedAsPsf:
781  continue
782  timg, tfoot = pkres.templateImage, pkres.templateFootprint
783  if bUtils.hasSignificantFluxAtEdge(timg, tfoot, 3*dp.avgNoise):
784  log.trace("Template %i has significant flux at edge: ramping", pkres.pki)
785  try:
786  (timg2, tfoot2, patched) = _handle_flux_at_edge(log, dp.psffwhm, timg, tfoot, dp.fp,
787  dp.maskedImage, dp.x0, dp.x1,
788  dp.y0, dp.y1, dp.psf, pkres.peak,
789  dp.avgNoise, patchEdges)
790  except lsst.pex.exceptions.Exception as exc:
791  if (isinstance(exc, lsst.pex.exceptions.InvalidParameterError)
792  and "CoaddPsf" in str(exc)):
793  pkres.setOutOfBounds()
794  continue
795  raise
796  pkres.setRampedTemplate(timg2, tfoot2)
797  if patched:
798  pkres.setPatched()
799  pkres.setTemplate(timg2, tfoot2)
800  modified = True
801  return modified
802 
803 

◆ reconstructTemplates()

def lsst.meas.deblender.plugins.reconstructTemplates (   debResult,
  log,
  maxTempDotProd = 0.5 
)
Remove "degenerate templates"

If galaxies have substructure, such as face-on spirals, the process of identifying peaks can
"shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite
similar because they represent the same galaxy, so we try to identify these "degenerate" peaks
by looking at the inner product (in pixel space) of pairs of templates.
If they are nearly parallel, we only keep one of the peaks and reject the other.
If only one of the peaks is a PSF template, the other template is used,
otherwise the one with the maximum template value is kept.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
maxTempDotProd: `float`, optional
    All dot products between templates greater than ``maxTempDotProd`` will result in one
    of the templates removed.

Returns
-------
modified: `bool`
    If any degenerate templates are found, ``modified`` is ``True``.

Definition at line 1116 of file plugins.py.

1116 def reconstructTemplates(debResult, log, maxTempDotProd=0.5):
1117  """Remove "degenerate templates"
1118 
1119  If galaxies have substructure, such as face-on spirals, the process of identifying peaks can
1120  "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite
1121  similar because they represent the same galaxy, so we try to identify these "degenerate" peaks
1122  by looking at the inner product (in pixel space) of pairs of templates.
1123  If they are nearly parallel, we only keep one of the peaks and reject the other.
1124  If only one of the peaks is a PSF template, the other template is used,
1125  otherwise the one with the maximum template value is kept.
1126 
1127  Parameters
1128  ----------
1129  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1130  Container for the final deblender results.
1131  log: `log.Log`
1132  LSST logger for logging purposes.
1133  maxTempDotProd: `float`, optional
1134  All dot products between templates greater than ``maxTempDotProd`` will result in one
1135  of the templates removed.
1136 
1137  Returns
1138  -------
1139  modified: `bool`
1140  If any degenerate templates are found, ``modified`` is ``True``.
1141  """
1142  log.trace('Looking for degnerate templates')
1143 
1144  foundReject = False
1145  for fidx in debResult.filters:
1146  dp = debResult.deblendedParents[fidx]
1147  nchild = np.sum([pkres.skip is False for pkres in dp.peaks])
1148  indexes = [pkres.pki for pkres in dp.peaks if pkres.skip is False]
1149 
1150  # We build a matrix that stores the dot product between templates.
1151  # We convert the template images to HeavyFootprints because they already have a method
1152  # to compute the dot product.
1153  A = np.zeros((nchild, nchild))
1154  maxTemplate = []
1155  heavies = []
1156  for pkres in dp.peaks:
1157  if pkres.skip:
1158  continue
1159  heavies.append(afwDet.makeHeavyFootprint(pkres.templateFootprint,
1160  afwImage.MaskedImageF(pkres.templateImage)))
1161  maxTemplate.append(np.max(pkres.templateImage.getArray()))
1162 
1163  for i in range(nchild):
1164  for j in range(i + 1):
1165  A[i, j] = heavies[i].dot(heavies[j])
1166 
1167  # Normalize the dot products to get the cosine of the angle between templates
1168  for i in range(nchild):
1169  for j in range(i):
1170  norm = A[i, i]*A[j, j]
1171  if norm <= 0:
1172  A[i, j] = 0
1173  else:
1174  A[i, j] /= np.sqrt(norm)
1175 
1176  # Iterate over pairs of objects and find the maximum non-diagonal element of the matrix.
1177  # Exit the loop once we find a single degenerate pair greater than the threshold.
1178  rejectedIndex = -1
1179  for i in range(nchild):
1180  currentMax = 0.
1181  for j in range(i):
1182  if A[i, j] > currentMax:
1183  currentMax = A[i, j]
1184  if currentMax > maxTempDotProd:
1185  foundReject = True
1186  rejectedIndex = j
1187 
1188  if foundReject:
1189  break
1190 
1191  del A
1192 
1193  # If one of the objects is identified as a PSF keep the other one, otherwise keep the one
1194  # with the maximum template value
1195  if foundReject:
1196  keep = indexes[i]
1197  reject = indexes[rejectedIndex]
1198  if dp.peaks[keep].deblendedAsPsf and dp.peaks[reject].deblendedAsPsf is False:
1199  keep = indexes[rejectedIndex]
1200  reject = indexes[i]
1201  elif dp.peaks[keep].deblendedAsPsf is False and dp.peaks[reject].deblendedAsPsf:
1202  reject = indexes[rejectedIndex]
1203  keep = indexes[i]
1204  else:
1205  if maxTemplate[rejectedIndex] > maxTemplate[i]:
1206  keep = indexes[rejectedIndex]
1207  reject = indexes[i]
1208  log.trace('Removing object with index %d : %f. Degenerate with %d' % (reject, currentMax,
1209  keep))
1210  dp.peaks[reject].skip = True
1211  dp.peaks[reject].degenerate = True
1212 
1213  return foundReject
1214 
1215 

◆ weightTemplates()

def lsst.meas.deblender.plugins.weightTemplates (   debResult,
  log 
)
Weight the templates to best fit the observed image in each filter

This function re-weights the templates so that their linear combination best represents
the observed image in that filter.
In the future it may be useful to simultaneously weight all of the filters together.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.

Returns
-------
modified: `bool`
    ``weightTemplates`` does not actually modify the ``Footprint`` templates other than
    to add a weight to them, so ``modified`` is always ``False``.

Definition at line 1047 of file plugins.py.

1047 def weightTemplates(debResult, log):
1048  """Weight the templates to best fit the observed image in each filter
1049 
1050  This function re-weights the templates so that their linear combination best represents
1051  the observed image in that filter.
1052  In the future it may be useful to simultaneously weight all of the filters together.
1053 
1054  Parameters
1055  ----------
1056  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1057  Container for the final deblender results.
1058  log: `log.Log`
1059  LSST logger for logging purposes.
1060 
1061  Returns
1062  -------
1063  modified: `bool`
1064  ``weightTemplates`` does not actually modify the ``Footprint`` templates other than
1065  to add a weight to them, so ``modified`` is always ``False``.
1066  """
1067  # Weight the templates by doing a least-squares fit to the image
1068  log.trace('Weighting templates')
1069  for fidx in debResult.filters:
1070  _weightTemplates(debResult.deblendedParents[fidx])
1071  return False
1072 
1073 
lsst::afw::geom::Span
A range of pixels within one row of an Image.
Definition: Span.h:47
lsst::afw::detection::makeHeavyFootprint
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...
Definition: HeavyFootprint.h:148
lsst::afw::image::Mask
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
lsst::meas::deblender.plugins.weightTemplates
def weightTemplates(debResult, log)
Definition: plugins.py:1047
lsst::meas::deblender.plugins.fitPsfs
def fitPsfs(debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2)
Definition: plugins.py:154
lsst::meas::deblender.plugins.reconstructTemplates
def reconstructTemplates(debResult, log, maxTempDotProd=0.5)
Definition: plugins.py:1116
lsst::afw.display.ds9.dot
def dot(symb, c, r, frame=None, size=2, ctype=None, origin=afwImage.PARENT, *args, **kwargs)
Definition: ds9.py:101
lsst::afw::geom::SpanSet
A compact representation of a collection of pixels.
Definition: SpanSet.h:78
lsst::meas::deblender.plugins.clipFootprintToNonzeroImpl
def clipFootprintToNonzeroImpl(foot, image)
Definition: plugins.py:34
lsst::meas::deblender.plugins.makeTemplatesMonotonic
def makeTemplatesMonotonic(debResult, log)
Definition: plugins.py:978
lsst::meas::deblender.plugins.medianSmoothTemplates
def medianSmoothTemplates(debResult, log, medianFilterHalfsize=2)
Definition: plugins.py:933
lsst::pex::exceptions::InvalidParameterError
Reports invalid arguments.
Definition: Runtime.h:66
lsst::geom::Point< int, 2 >
lsst::meas::deblender.plugins.apportionFlux
def apportionFlux(debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak', strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001, getTemplateSum=False)
Definition: plugins.py:1216
lsst::pex::exceptions::Exception
Provides consistent interface for LSST exceptions.
Definition: Exception.h:107
lsst::meas::deblender.plugins.buildSymmetricTemplates
def buildSymmetricTemplates(debResult, log, patchEdges=False, setOrigTemplate=True)
Definition: plugins.py:689
lsst::meas::deblender.plugins.rampFluxAtEdge
def rampFluxAtEdge(debResult, log, patchEdges=False)
Definition: plugins.py:750
lsst::meas::deblender.plugins.clipFootprintsToNonzero
def clipFootprintsToNonzero(debResult, log)
Definition: plugins.py:1013