LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
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 1250 of file plugins.py.

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

◆ 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 708 of file plugins.py.

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

◆ 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 1041 of file plugins.py.

1041 def clipFootprintsToNonzero(debResult, log):
1042  r"""Clip non-zero spans in the template footprints for every peak in each filter.
1043 
1044  Peak ``Footprint``\ s are clipped to the region in the image containing
1045  non-zero values by dropping spans that are completely zero and moving
1046  endpoints to non-zero pixels (but does not split spans that have
1047  internal zeros).
1048 
1049  Parameters
1050  ----------
1051  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1052  Container for the final deblender results.
1053  log: `log.Log`
1054  LSST logger for logging purposes.
1055 
1056  Returns
1057  -------
1058  modified: `bool`
1059  Whether or not any templates were modified.
1060  This will be ``True`` as long as there is at least one source that
1061  is not flagged as a PSF.
1062  """
1063  # Loop over all filters
1064  for fidx in debResult.filters:
1065  dp = debResult.deblendedParents[fidx]
1066  for peaki, pkres in enumerate(dp.peaks):
1067  if pkres.skip or pkres.deblendedAsPsf:
1068  continue
1069  timg, tfoot = pkres.templateImage, pkres.templateFootprint
1070  clipFootprintToNonzeroImpl(tfoot, timg)
1071  if not tfoot.getBBox().isEmpty() and tfoot.getBBox() != timg.getBBox(afwImage.PARENT):
1072  timg = timg.Factory(timg, tfoot.getBBox(), afwImage.PARENT, True)
1073  pkres.setTemplate(timg, tfoot)
1074  return False
1075 
1076 
def clipFootprintToNonzeroImpl(foot, image)
Definition: plugins.py:38
def clipFootprintsToNonzero(debResult, log)
Definition: plugins.py:1041

◆ 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 38 of file plugins.py.

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

◆ 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 164 of file plugins.py.

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

◆ 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 1005 of file plugins.py.

1005 def makeTemplatesMonotonic(debResult, log):
1006  """Make the templates monotonic.
1007 
1008  The pixels in the templates are modified such that pixels further
1009  from the peak will have values smaller than those closer to the peak.
1010 
1011  Parameters
1012  ----------
1013  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1014  Container for the final deblender results.
1015  log: `log.Log`
1016  LSST logger for logging purposes.
1017 
1018  Returns
1019  -------
1020  modified: `bool`
1021  Whether or not any templates were modified.
1022  This will be ``True`` as long as there is at least one source that
1023  is not flagged as a PSF.
1024  """
1025  modified = False
1026  # Loop over all filters
1027  for fidx in debResult.filters:
1028  dp = debResult.deblendedParents[fidx]
1029  for peaki, pkres in enumerate(dp.peaks):
1030  if pkres.skip or pkres.deblendedAsPsf:
1031  continue
1032  modified = True
1033  timg, tfoot = pkres.templateImage, pkres.templateFootprint
1034  pk = pkres.peak
1035  log.trace('Making template %i monotonic', pkres.pki)
1036  bUtils.makeMonotonic(timg, pk)
1037  pkres.setTemplate(timg, tfoot)
1038  return modified
1039 
1040 
def makeTemplatesMonotonic(debResult, log)
Definition: plugins.py:1005

◆ 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 956 of file plugins.py.

956 def medianSmoothTemplates(debResult, log, medianFilterHalfsize=2):
957  """Applying median smoothing filter to the template images for every
958  peak in every filter.
959 
960  Parameters
961  ----------
962  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
963  Container for the final deblender results.
964  log: `log.Log`
965  LSST logger for logging purposes.
966  medianFilterHalfSize: `int`, optional
967  Half the box size of the median filter, i.e. a
968  ``medianFilterHalfSize`` of 50 means that each output pixel will
969  be the median of the pixels in a 101 x 101-pixel box in the input
970  image. This parameter is only used when
971  ``medianSmoothTemplate==True``, otherwise it is ignored.
972 
973  Returns
974  -------
975  modified: `bool`
976  Whether or not any templates were modified.
977  This will be ``True`` as long as there is at least one source that
978  is not flagged as a PSF.
979  """
980  modified = False
981  # Loop over all filters
982  for fidx in debResult.filters:
983  dp = debResult.deblendedParents[fidx]
984  for peaki, pkres in enumerate(dp.peaks):
985  if pkres.skip or pkres.deblendedAsPsf:
986  continue
987  modified = True
988  timg, tfoot = pkres.templateImage, pkres.templateFootprint
989  filtsize = medianFilterHalfsize*2 + 1
990  if timg.getWidth() >= filtsize and timg.getHeight() >= filtsize:
991  log.trace('Median filtering template %i', pkres.pki)
992  # We want the output to go in "t1", so copy it into
993  # "inimg" for input
994  inimg = timg.Factory(timg, True)
995  bUtils.medianFilter(inimg, timg, medianFilterHalfsize)
996  # possible save this median-filtered template
997  pkres.setMedianFilteredTemplate(timg, tfoot)
998  else:
999  log.trace('Not median-filtering template %i: size %i x %i smaller than required %i x %i',
1000  pkres.pki, timg.getWidth(), timg.getHeight(), filtsize, filtsize)
1001  pkres.setTemplate(timg, tfoot)
1002  return modified
1003 
1004 
def medianSmoothTemplates(debResult, log, medianFilterHalfsize=2)
Definition: plugins.py:956

◆ 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 ``~afw.detection.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 770 of file plugins.py.

770 def rampFluxAtEdge(debResult, log, patchEdges=False):
771  r"""Adjust flux on the edges of the template footprints.
772 
773  Using the PSF, a peak ``~afw.detection.Footprint`` with pixels on the edge
774  of ``footprint`` is grown by the ``psffwhm*1.5`` and filled in
775  with ramped pixels. The result is a new symmetric footprint
776  template for the peaks near the edge.
777 
778  Parameters
779  ----------
780  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
781  Container for the final deblender results.
782  log: `log.Log`
783  LSST logger for logging purposes.
784  patchEdges: `bool`, optional
785  If True and if the parent Footprint touches pixels with the
786  ``EDGE`` bit set, then grow the parent Footprint to include
787  all symmetric templates.
788 
789  Returns
790  -------
791  modified: `bool`
792  If any peaks have their templates modified to include flux at the
793  edges, ``modified`` is ``True``.
794  """
795  modified = False
796  # Loop over all filters
797  for fidx in debResult.filters:
798  dp = debResult.deblendedParents[fidx]
799  log.trace('Checking for significant flux at edge: sigma1=%g', dp.avgNoise)
800 
801  for peaki, pkres in enumerate(dp.peaks):
802  if pkres.skip or pkres.deblendedAsPsf:
803  continue
804  timg, tfoot = pkres.templateImage, pkres.templateFootprint
805  if bUtils.hasSignificantFluxAtEdge(timg, tfoot, 3*dp.avgNoise):
806  log.trace("Template %i has significant flux at edge: ramping", pkres.pki)
807  try:
808  (timg2, tfoot2, patched) = _handle_flux_at_edge(log, dp.psffwhm, timg, tfoot, dp.fp,
809  dp.maskedImage, dp.x0, dp.x1,
810  dp.y0, dp.y1, dp.psf, pkres.peak,
811  dp.avgNoise, patchEdges)
812  except lsst.pex.exceptions.Exception as exc:
813  if (isinstance(exc, lsst.pex.exceptions.InvalidParameterError)
814  and "CoaddPsf" in str(exc)):
815  pkres.setOutOfBounds()
816  continue
817  raise
818  pkres.setRampedTemplate(timg2, tfoot2)
819  if patched:
820  pkres.setPatched()
821  pkres.setTemplate(timg2, tfoot2)
822  modified = True
823  return modified
824 
825 
Provides consistent interface for LSST exceptions.
Definition: Exception.h:107
Reports invalid arguments.
Definition: Runtime.h:66
def rampFluxAtEdge(debResult, log, patchEdges=False)
Definition: plugins.py:770

◆ 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 1149 of file plugins.py.

1149 def reconstructTemplates(debResult, log, maxTempDotProd=0.5):
1150  """Remove "degenerate templates"
1151 
1152  If galaxies have substructure, such as face-on spirals, the process of
1153  identifying peaks can "shred" the galaxy into many pieces. The templates
1154  of shredded galaxies are typically quite similar because they represent
1155  the same galaxy, so we try to identify these "degenerate" peaks
1156  by looking at the inner product (in pixel space) of pairs of templates.
1157  If they are nearly parallel, we only keep one of the peaks and reject
1158  the other. If only one of the peaks is a PSF template, the other template
1159  is used, otherwise the one with the maximum template value is kept.
1160 
1161  Parameters
1162  ----------
1163  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1164  Container for the final deblender results.
1165  log: `log.Log`
1166  LSST logger for logging purposes.
1167  maxTempDotProd: `float`, optional
1168  All dot products between templates greater than ``maxTempDotProd``
1169  will result in one of the templates removed.
1170 
1171  Returns
1172  -------
1173  modified: `bool`
1174  If any degenerate templates are found, ``modified`` is ``True``.
1175  """
1176  log.trace('Looking for degnerate templates')
1177 
1178  foundReject = False
1179  for fidx in debResult.filters:
1180  dp = debResult.deblendedParents[fidx]
1181  nchild = np.sum([pkres.skip is False for pkres in dp.peaks])
1182  indexes = [pkres.pki for pkres in dp.peaks if pkres.skip is False]
1183 
1184  # We build a matrix that stores the dot product between templates.
1185  # We convert the template images to HeavyFootprints because they already have a method
1186  # to compute the dot product.
1187  A = np.zeros((nchild, nchild))
1188  maxTemplate = []
1189  heavies = []
1190  for pkres in dp.peaks:
1191  if pkres.skip:
1192  continue
1193  heavies.append(afwDet.makeHeavyFootprint(pkres.templateFootprint,
1194  afwImage.MaskedImageF(pkres.templateImage)))
1195  maxTemplate.append(np.max(pkres.templateImage.getArray()))
1196 
1197  for i in range(nchild):
1198  for j in range(i + 1):
1199  A[i, j] = heavies[i].dot(heavies[j])
1200 
1201  # Normalize the dot products to get the cosine of the angle between templates
1202  for i in range(nchild):
1203  for j in range(i):
1204  norm = A[i, i]*A[j, j]
1205  if norm <= 0:
1206  A[i, j] = 0
1207  else:
1208  A[i, j] /= np.sqrt(norm)
1209 
1210  # Iterate over pairs of objects and find the maximum non-diagonal element of the matrix.
1211  # Exit the loop once we find a single degenerate pair greater than the threshold.
1212  rejectedIndex = -1
1213  for i in range(nchild):
1214  currentMax = 0.
1215  for j in range(i):
1216  if A[i, j] > currentMax:
1217  currentMax = A[i, j]
1218  if currentMax > maxTempDotProd:
1219  foundReject = True
1220  rejectedIndex = j
1221 
1222  if foundReject:
1223  break
1224 
1225  del A
1226 
1227  # If one of the objects is identified as a PSF keep the other one, otherwise keep the one
1228  # with the maximum template value
1229  if foundReject:
1230  keep = indexes[i]
1231  reject = indexes[rejectedIndex]
1232  if dp.peaks[keep].deblendedAsPsf and dp.peaks[reject].deblendedAsPsf is False:
1233  keep = indexes[rejectedIndex]
1234  reject = indexes[i]
1235  elif dp.peaks[keep].deblendedAsPsf is False and dp.peaks[reject].deblendedAsPsf:
1236  reject = indexes[rejectedIndex]
1237  keep = indexes[i]
1238  else:
1239  if maxTemplate[rejectedIndex] > maxTemplate[i]:
1240  keep = indexes[rejectedIndex]
1241  reject = indexes[i]
1242  log.trace('Removing object with index %d : %f. Degenerate with %d',
1243  reject, currentMax, keep)
1244  dp.peaks[reject].skip = True
1245  dp.peaks[reject].degenerate = True
1246 
1247  return foundReject
1248 
1249 
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...
def dot(symb, c, r, frame=None, size=2, ctype=None, origin=afwImage.PARENT, *args, **kwargs)
Definition: ds9.py:100
def reconstructTemplates(debResult, log, maxTempDotProd=0.5)
Definition: plugins.py:1149

◆ 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 1077 of file plugins.py.

1077 def weightTemplates(debResult, log):
1078  """Weight the templates to best fit the observed image in each filter
1079 
1080  This function re-weights the templates so that their linear combination
1081  best represents the observed image in that filter.
1082  In the future it may be useful to simultaneously weight all of the
1083  filters together.
1084 
1085  Parameters
1086  ----------
1087  debResult: `lsst.meas.deblender.baseline.DeblenderResult`
1088  Container for the final deblender results.
1089  log: `log.Log`
1090  LSST logger for logging purposes.
1091 
1092  Returns
1093  -------
1094  modified: `bool`
1095  ``weightTemplates`` does not actually modify the ``Footprint``
1096  templates other than to add a weight to them, so ``modified``
1097  is always ``False``.
1098  """
1099  # Weight the templates by doing a least-squares fit to the image
1100  log.trace('Weighting templates')
1101  for fidx in debResult.filters:
1102  _weightTemplates(debResult.deblendedParents[fidx])
1103  return False
1104 
1105 
def weightTemplates(debResult, log)
Definition: plugins.py:1077