LSST Applications g02d81e74bb+86cf3d8bc9,g180d380827+7a4e862ed4,g2079a07aa2+86d27d4dc4,g2305ad1205+e1ca1c66fa,g29320951ab+012e1474a1,g295015adf3+341ea1ce94,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g33d1c0ed96+0e5473021a,g3a166c0a6a+0e5473021a,g3ddfee87b4+c429d67c83,g48712c4677+f88676dd22,g487adcacf7+27e1e21933,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+b41db86c35,g5a732f18d5+53520f316c,g64a986408d+86cf3d8bc9,g858d7b2824+86cf3d8bc9,g8a8a8dda67+585e252eca,g99cad8db69+84912a7fdc,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,ga8c6da7877+a2b54eae19,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+6681f309db,gc120e1dc64+f0fcc2f6d8,gc28159a63d+0e5473021a,gcf0d15dbbd+c429d67c83,gdaeeff99f8+f9a426f77a,ge6526c86ff+0433e6603d,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gff1a9f87cc+86cf3d8bc9,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
Classes | Functions
lsst.meas.deblender.plugins Namespace Reference

Classes

class  DeblenderPlugin
 

Functions

 clipFootprintToNonzeroImpl (foot, image)
 
 _setPeakError (debResult, log, pk, cx, cy, filters, msg, flag)
 
 fitPsfs (debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2)
 
 _fitPsf (fp, fmask, pk, pkF, pkres, fbb, peaks, peaksF, log, psf, psffwhm, img, varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b, tinyFootprintSize=2)
 
 buildSymmetricTemplates (debResult, log, patchEdges=False, setOrigTemplate=True)
 
 rampFluxAtEdge (debResult, log, patchEdges=False)
 
 _handle_flux_at_edge (log, psffwhm, t1, tfoot, fp, maskedImage, x0, x1, y0, y1, psf, pk, sigma1, patchEdges)
 
 medianSmoothTemplates (debResult, log, medianFilterHalfsize=2)
 
 makeTemplatesMonotonic (debResult, log)
 
 clipFootprintsToNonzero (debResult, log)
 
 weightTemplates (debResult, log)
 
 _weightTemplates (dp)
 
 reconstructTemplates (debResult, log, maxTempDotProd=0.5)
 
 apportionFlux (debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak', strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001, getTemplateSum=False)
 

Function Documentation

◆ _fitPsf()

lsst.meas.deblender.plugins._fitPsf ( fp,
fmask,
pk,
pkF,
pkres,
fbb,
peaks,
peaksF,
log,
psf,
psffwhm,
img,
varimg,
psfChisqCut1,
psfChisqCut2,
psfChisqCut2b,
tinyFootprintSize = 2 )
protected
Fit a PSF + smooth background model (linear) to a small region
around a peak.

See fitPsfs for a more thorough description, including all
parameters not described below.

Parameters
----------
fp: `afw.detection.Footprint`
Footprint containing the Peaks to model.
fmask: `afw.image.Mask`
The Mask plane for pixels in the Footprint
pk: `afw.detection.PeakRecord`
The peak within the Footprint that we are going to fit with PSF model
pkF: `afw.geom.Point2D`
Floating point coordinates of the peak.
pkres: `meas.deblender.DeblendedPeak`
Peak results object that will hold the results.
fbb: `afw.geom.Box2I`
Bounding box of ``fp``
peaks: `afw.detection.PeakCatalog`
    Catalog of peaks contained in the parent footprint.
peaksF: list of `afw.geom.Point2D`
List of floating point coordinates of all of the peaks.
psf: list of `afw.detection.Psf`\ s
Psf of the ``maskedImage`` for each band.
psffwhm: list pf `float`\ s
FWHM of the ``maskedImage``\ 's ``psf`` in each band.
img: `afw.image.ImageF`
The image that contains the footprint.
varimg: `afw.image.ImageF`
The variance of the image that contains the footprint.

Results
-------
ispsf: `bool`
Whether or not the peak matches a PSF model.

Definition at line 239 of file plugins.py.

242 ):
243 r"""Fit a PSF + smooth background model (linear) to a small region
244 around a peak.
245
246 See fitPsfs for a more thorough description, including all
247 parameters not described below.
248
249 Parameters
250 ----------
251 fp: `afw.detection.Footprint`
252 Footprint containing the Peaks to model.
253 fmask: `afw.image.Mask`
254 The Mask plane for pixels in the Footprint
255 pk: `afw.detection.PeakRecord`
256 The peak within the Footprint that we are going to fit with PSF model
257 pkF: `afw.geom.Point2D`
258 Floating point coordinates of the peak.
259 pkres: `meas.deblender.DeblendedPeak`
260 Peak results object that will hold the results.
261 fbb: `afw.geom.Box2I`
262 Bounding box of ``fp``
263 peaks: `afw.detection.PeakCatalog`
264 Catalog of peaks contained in the parent footprint.
265 peaksF: list of `afw.geom.Point2D`
266 List of floating point coordinates of all of the peaks.
267 psf: list of `afw.detection.Psf`\ s
268 Psf of the ``maskedImage`` for each band.
269 psffwhm: list pf `float`\ s
270 FWHM of the ``maskedImage``\ 's ``psf`` in each band.
271 img: `afw.image.ImageF`
272 The image that contains the footprint.
273 varimg: `afw.image.ImageF`
274 The variance of the image that contains the footprint.
275
276 Results
277 -------
278 ispsf: `bool`
279 Whether or not the peak matches a PSF model.
280 """
281 import lsstDebug
282
283 # my __name__ is lsst.meas.deblender.baseline
284 debugPlots = lsstDebug.Info(__name__).plots
285 debugPsf = lsstDebug.Info(__name__).psf
286
287 # The small region is a disk out to R0, plus a ramp with
288 # decreasing weight down to R1.
289 R0 = int(np.ceil(psffwhm*1.))
290 # ramp down to zero weight at this radius...
291 R1 = int(np.ceil(psffwhm*1.5))
292 cx, cy = pkF.getX(), pkF.getY()
293 psfimg = psf.computeImage(cx, cy)
294 # R2: distance to neighbouring peak in order to put it into the model
295 R2 = R1 + min(psfimg.getWidth(), psfimg.getHeight())/2.
296
297 pbb = psfimg.getBBox()
298 pbb.clip(fbb)
299 px0, py0 = psfimg.getX0(), psfimg.getY0()
300
301 # Make sure we haven't been given a substitute PSF that's nowhere near where we want, as may occur if
302 # "Cannot compute CoaddPsf at point (xx,yy); no input images at that point."
303 if not pbb.contains(geom.Point2I(int(cx), int(cy))):
304 pkres.setOutOfBounds()
305 return
306
307 # The bounding-box of the local region we are going to fit ("stamp")
308 xlo = int(np.floor(cx - R1))
309 ylo = int(np.floor(cy - R1))
310 xhi = int(np.ceil(cx + R1))
311 yhi = int(np.ceil(cy + R1))
312 stampbb = geom.Box2I(geom.Point2I(xlo, ylo), geom.Point2I(xhi, yhi))
313 stampbb.clip(fbb)
314 xlo, xhi = stampbb.getMinX(), stampbb.getMaxX()
315 ylo, yhi = stampbb.getMinY(), stampbb.getMaxY()
316 if xlo > xhi or ylo > yhi:
317 log.trace('Skipping this peak: out of bounds')
318 pkres.setOutOfBounds()
319 return
320
321 # drop tiny footprints too?
322 if min(stampbb.getWidth(), stampbb.getHeight()) <= max(tinyFootprintSize, 2):
323 # Minimum size limit of 2 comes from the "PSF dx" calculation, which involves shifting the PSF
324 # by one pixel to the left and right.
325 log.trace('Skipping this peak: tiny footprint / close to edge')
326 pkres.setTinyFootprint()
327 return
328
329 # find other peaks within range...
330 otherpeaks = []
331 for pk2, pkF2 in zip(peaks, peaksF):
332 if pk2 == pk:
333 continue
334 if pkF.distanceSquared(pkF2) > R2**2:
335 continue
336 opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
337 if not opsfimg.getBBox().overlaps(stampbb):
338 continue
339 otherpeaks.append(opsfimg)
340 log.trace('%i other peaks within range', len(otherpeaks))
341
342 # Now we are going to do a least-squares fit for the flux in this
343 # PSF, plus a decenter term, a linear sky, and fluxes of nearby
344 # sources (assumed point sources). Build up the matrix...
345 # Number of terms -- PSF flux, constant sky, X, Y, + other PSF fluxes
346 NT1 = 4 + len(otherpeaks)
347 # + PSF dx, dy
348 NT2 = NT1 + 2
349 # Number of pixels -- at most
350 NP = (1 + yhi - ylo)*(1 + xhi - xlo)
351 # indices of columns in the "A" matrix.
352 I_psf = 0
353 I_sky = 1
354 I_sky_ramp_x = 2
355 I_sky_ramp_y = 3
356 # offset of other psf fluxes:
357 I_opsf = 4
358 I_dx = NT1 + 0
359 I_dy = NT1 + 1
360
361 # Build the matrix "A", rhs "b" and weight "w".
362 ix0, iy0 = img.getX0(), img.getY0()
363 fx0, fy0 = fbb.getMinX(), fbb.getMinY()
364 fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
365 islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
366 fmask_sub = fmask .getArray()[fslice]
367 var_sub = varimg.getArray()[islice]
368 img_sub = img.getArray()[islice]
369
370 # Clip the PSF image to match its bbox
371 psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
372 pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
373 px0, px1 = pbb.getMinX(), pbb.getMaxX()
374 py0, py1 = pbb.getMinY(), pbb.getMaxY()
375
376 # Compute the "valid" pixels within our region-of-interest
377 valid = (fmask_sub > 0)
378 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
379 RR = ((xx - cx)**2)[np.newaxis, :] + ((yy - cy)**2)[:, np.newaxis]
380 valid *= (RR <= R1**2)
381 valid *= (var_sub > 0)
382 NP = valid.sum()
383
384 if NP == 0:
385 log.warning('Skipping peak at (%.1f, %.1f): no unmasked pixels nearby', cx, cy)
386 pkres.setNoValidPixels()
387 return
388
389 # pixel coords of valid pixels
390 XX, YY = np.meshgrid(xx, yy)
391 ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
392
393 inpsfx = (xx >= px0)*(xx <= px1)
394 inpsfy = (yy >= py0)*(yy <= py1)
395 inpsf = np.outer(inpsfy, inpsfx)
396 indx = np.outer(inpsfy, (xx > px0)*(xx < px1))
397 indy = np.outer((yy > py0)*(yy < py1), inpsfx)
398
399 del inpsfx
400 del inpsfy
401
402 def _overlap(xlo, xhi, xmin, xmax):
403 assert (xlo <= xmax) and (xhi >= xmin) and (xlo <= xhi) and (xmin <= xmax)
404 xloclamp = max(xlo, xmin)
405 Xlo = xloclamp - xlo
406 xhiclamp = min(xhi, xmax)
407 Xhi = Xlo + (xhiclamp - xloclamp)
408 assert xloclamp >= 0
409 assert Xlo >= 0
410 return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
411
412 A = np.zeros((NP, NT2))
413 # Constant term
414 A[:, I_sky] = 1.
415 # Sky slope terms: dx, dy
416 A[:, I_sky_ramp_x] = ipixes[:, 0] + (xlo-cx)
417 A[:, I_sky_ramp_y] = ipixes[:, 1] + (ylo-cy)
418
419 # whew, grab the valid overlapping PSF pixels
420 px0, px1 = pbb.getMinX(), pbb.getMaxX()
421 py0, py1 = pbb.getMinY(), pbb.getMaxY()
422 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
423 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
424 dpx0, dpy0 = px0 - xlo, py0 - ylo
425 psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
426 psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
427 psfsub = psfarr[psf_y_slice, psf_x_slice]
428 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
429 A[inpsf[valid], I_psf] = psfsub[vsub]
430
431 # PSF dx -- by taking the half-difference of shifted-by-one and
432 # shifted-by-minus-one.
433 oldsx = (sx1, sx2, sx3, sx4)
434 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0+1, px1-1)
435 psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1]
436 - psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1])/2.
437 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
438 A[indx[valid], I_dx] = psfsub[vsub]
439 # revert x indices...
440 (sx1, sx2, sx3, sx4) = oldsx
441
442 # PSF dy
443 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0+1, py1-1)
444 psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice]
445 - psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice])/2.
446 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
447 A[indy[valid], I_dy] = psfsub[vsub]
448
449 # other PSFs...
450 for j, opsf in enumerate(otherpeaks):
451 obb = opsf.getBBox()
452 ino = np.outer((yy >= obb.getMinY())*(yy <= obb.getMaxY()),
453 (xx >= obb.getMinX())*(xx <= obb.getMaxX()))
454 dpx0, dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
455 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
456 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
457 opsfarr = opsf.getArray()
458 psfsub = opsfarr[sy3 - dpy0: sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
459 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
460 A[ino[valid], I_opsf + j] = psfsub[vsub]
461
462 b = img_sub[valid]
463
464 # Weights -- from ramp and image variance map.
465 # Ramp weights -- from 1 at R0 down to 0 at R1.
466 rw = np.ones_like(RR)
467 ii = (RR > R0**2)
468 rr = np.sqrt(RR[ii])
469 rw[ii] = np.maximum(0, 1. - ((rr - R0)/(R1 - R0)))
470 w = np.sqrt(rw[valid]/var_sub[valid])
471 # save the effective number of pixels
472 sumr = np.sum(rw[valid])
473 log.debug('sumr = %g', sumr)
474
475 del ii
476
477 Aw = A*w[:, np.newaxis]
478 bw = b*w
479
480 if debugPlots:
481 import pylab as plt
482 plt.clf()
483 N = NT2 + 2
484 R, C = 2, (N+1)/2
485 for i in range(NT2):
486 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
487 im1[ipixes[:, 1], ipixes[:, 0]] = A[:, i]
488 plt.subplot(R, C, i+1)
489 plt.imshow(im1, interpolation='nearest', origin='lower')
490 plt.subplot(R, C, NT2+1)
491 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
492 im1[ipixes[:, 1], ipixes[:, 0]] = b
493 plt.imshow(im1, interpolation='nearest', origin='lower')
494 plt.subplot(R, C, NT2+2)
495 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
496 im1[ipixes[:, 1], ipixes[:, 0]] = w
497 plt.imshow(im1, interpolation='nearest', origin='lower')
498 plt.savefig('A.png')
499
500 # We do fits with and without the decenter (dx,dy) terms.
501 # Since the dx,dy terms are at the end of the matrix,
502 # we can do that just by trimming off those elements.
503 #
504 # The SVD can fail if there are NaNs in the matrices; this should
505 # really be handled upstream
506 try:
507 # NT1 is number of terms without dx,dy;
508 # X1 is the result without decenter
509 X1, r1, rank1, s1 = np.linalg.lstsq(Aw[:, :NT1], bw, rcond=-1)
510 # X2 is with decenter
511 X2, r2, rank2, s2 = np.linalg.lstsq(Aw, bw, rcond=-1)
512 except np.linalg.LinAlgError as e:
513 log.warning("Failed to fit PSF to child: %s", e)
514 pkres.setPsfFitFailed()
515 return
516
517 log.debug('r1 r2 %s %s', r1, r2)
518
519 # r is weighted chi-squared = sum over pixels: ramp * (model -
520 # data)**2/sigma**2
521 if len(r1) > 0:
522 chisq1 = r1[0]
523 else:
524 chisq1 = 1e30
525 if len(r2) > 0:
526 chisq2 = r2[0]
527 else:
528 chisq2 = 1e30
529 dof1 = sumr - len(X1)
530 dof2 = sumr - len(X2)
531 log.debug('dof1, dof2 %g %g', dof1, dof2)
532
533 # This can happen if we're very close to the edge (?)
534 if dof1 <= 0 or dof2 <= 0:
535 log.trace('Skipping this peak: bad DOF %g, %g', dof1, dof2)
536 pkres.setBadPsfDof()
537 return
538
539 q1 = chisq1/dof1
540 q2 = chisq2/dof2
541 log.trace('PSF fits: chisq/dof = %g, %g', q1, q2)
542 ispsf1 = (q1 < psfChisqCut1)
543 ispsf2 = (q2 < psfChisqCut2)
544
545 pkres.psfFit1 = (chisq1, dof1)
546 pkres.psfFit2 = (chisq2, dof2)
547
548 # check that the fit PSF spatial derivative terms aren't too big
549 if ispsf2:
550 fdx, fdy = X2[I_dx], X2[I_dy]
551 f0 = X2[I_psf]
552 # as a fraction of the PSF flux
553 dx = fdx/f0
554 dy = fdy/f0
555 ispsf2 = ispsf2 and (abs(dx) < 1. and abs(dy) < 1.)
556 log.trace('isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s', dx, dy, str(ispsf2))
557 if not ispsf2:
558 pkres.psfFitBigDecenter = True
559
560 # Looks like a shifted PSF: try actually shifting the PSF by that amount
561 # and re-evaluate the fit.
562 if ispsf2:
563 psfimg2 = psf.computeImage(cx + dx, cy + dy)
564 # clip
565 pbb2 = psfimg2.getBBox()
566 pbb2.clip(fbb)
567
568 # Make sure we haven't been given a substitute PSF that's nowhere near where we want, as may occur if
569 # "Cannot compute CoaddPsf at point (xx,yy); no input images at that point."
570 if not pbb2.contains(geom.Point2I(int(cx + dx), int(cy + dy))):
571 ispsf2 = False
572 else:
573 # clip image to bbox
574 px0, py0 = psfimg2.getX0(), psfimg2.getY0()
575 psfarr = psfimg2.getArray()[pbb2.getMinY()-py0:1+pbb2.getMaxY()-py0,
576 pbb2.getMinX()-px0:1+pbb2.getMaxX()-px0]
577 px0, py0 = pbb2.getMinX(), pbb2.getMinY()
578 px1, py1 = pbb2.getMaxX(), pbb2.getMaxY()
579
580 # yuck! Update the PSF terms in the least-squares fit matrix.
581 Ab = A[:, :NT1]
582
583 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
584 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
585 dpx0, dpy0 = px0 - xlo, py0 - ylo
586 psfsub = psfarr[sy3-dpy0:sy4-dpy0, sx3-dpx0:sx4-dpx0]
587 vsub = valid[sy1-ylo:sy2-ylo, sx1-xlo:sx2-xlo]
588 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
589 inpsf = np.outer((yy >= py0)*(yy <= py1), (xx >= px0)*(xx <= px1))
590 Ab[inpsf[valid], I_psf] = psfsub[vsub]
591
592 Aw = Ab*w[:, np.newaxis]
593 # re-solve...
594 Xb, rb, rankb, sb = np.linalg.lstsq(Aw, bw, rcond=-1)
595 if len(rb) > 0:
596 chisqb = rb[0]
597 else:
598 chisqb = 1e30
599 dofb = sumr - len(Xb)
600 qb = chisqb/dofb
601 ispsf2 = (qb < psfChisqCut2b)
602 q2 = qb
603 X2 = Xb
604 log.trace('shifted PSF: new chisq/dof = %g; good? %s', qb, ispsf2)
605 pkres.psfFit3 = (chisqb, dofb)
606
607 # Which one do we keep?
608 if (((ispsf1 and ispsf2) and (q2 < q1))
609 or (ispsf2 and not ispsf1)):
610 Xpsf = X2
611 chisq = chisq2
612 dof = dof2
613 log.debug('dof %g', dof)
614 log.trace('Keeping shifted-PSF model')
615 cx += dx
616 cy += dy
617 pkres.psfFitWithDecenter = True
618 else:
619 # (arbitrarily set to X1 when neither fits well)
620 Xpsf = X1
621 chisq = chisq1
622 dof = dof1
623 log.debug('dof %g', dof)
624 log.trace('Keeping unshifted PSF model')
625
626 ispsf = (ispsf1 or ispsf2)
627
628 # Save the PSF models in images for posterity.
629 if debugPsf:
630 SW, SH = 1+xhi-xlo, 1+yhi-ylo
631 psfmod = afwImage.ImageF(SW, SH)
632 psfmod.setXY0(xlo, ylo)
633 psfderivmodm = afwImage.MaskedImageF(SW, SH)
634 psfderivmod = psfderivmodm.getImage()
635 psfderivmod.setXY0(xlo, ylo)
636 model = afwImage.ImageF(SW, SH)
637 model.setXY0(xlo, ylo)
638 for i in range(len(Xpsf)):
639 for (x, y), v in zip(ipixes, A[:, i]*Xpsf[i]):
640 ix, iy = int(x), int(y)
641 model.set(ix, iy, model.get(ix, iy) + float(v))
642 if i in [I_psf, I_dx, I_dy]:
643 psfderivmod.set(ix, iy, psfderivmod.get(ix, iy) + float(v))
644 for ii in range(NP):
645 x, y = ipixes[ii, :]
646 psfmod.set(int(x), int(y), float(A[ii, I_psf]*Xpsf[I_psf]))
647 modelfp = afwDet.Footprint(fp.getPeaks().getSchema())
648 for (x, y) in ipixes:
649 modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
650 modelfp.normalize()
651
652 pkres.psfFitDebugPsf0Img = psfimg
653 pkres.psfFitDebugPsfImg = psfmod
654 pkres.psfFitDebugPsfDerivImg = psfderivmod
655 pkres.psfFitDebugPsfModel = model
656 pkres.psfFitDebugStamp = img.Factory(img, stampbb, True)
657 pkres.psfFitDebugValidPix = valid # numpy array
658 pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb, True)
659 ww = np.zeros(valid.shape, np.float64)
660 ww[valid] = w
661 pkres.psfFitDebugWeight = ww # numpy
662 pkres.psfFitDebugRampWeight = rw
663
664 # Save things we learned about this peak for posterity...
665 pkres.psfFitR0 = R0
666 pkres.psfFitR1 = R1
667 pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
668 pkres.psfFitCenter = (cx, cy)
669 log.debug('saving chisq,dof %g %g', chisq, dof)
670 pkres.psfFitBest = (chisq, dof)
671 pkres.psfFitParams = Xpsf
672 pkres.psfFitFlux = Xpsf[I_psf]
673 pkres.psfFitNOthers = len(otherpeaks)
674
675 if ispsf:
676 pkres.setDeblendedAsPsf()
677
678 # replace the template image by the PSF + derivatives
679 # image.
680 log.trace('Deblending as PSF; setting template to PSF model')
681
682 # Instantiate the PSF model and clip it to the footprint
683 psfimg = psf.computeImage(cx, cy)
684 # Scale by fit flux.
685 psfimg *= Xpsf[I_psf]
686 psfimg = psfimg.convertF()
687
688 # Clip the Footprint to the PSF model image bbox.
689 fpcopy = afwDet.Footprint(fp)
690 psfbb = psfimg.getBBox()
691 fpcopy.clipTo(psfbb)
692 bb = fpcopy.getBBox()
693
694 # Copy the part of the PSF model within the clipped footprint.
695 psfmod = afwImage.ImageF(bb)
696 fpcopy.spans.copyImage(psfimg, psfmod)
697 # Save it as our template.
698 clipFootprintToNonzeroImpl(fpcopy, psfmod)
699 pkres.setTemplate(psfmod, fpcopy)
700
701 # DEBUG
702 pkres.setPsfTemplate(psfmod, fpcopy)
703
704 return ispsf
705
706
int min
int max
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
An integer coordinate rectangle.
Definition Box.h:55

◆ _handle_flux_at_edge()

lsst.meas.deblender.plugins._handle_flux_at_edge ( log,
psffwhm,
t1,
tfoot,
fp,
maskedImage,
x0,
x1,
y0,
y1,
psf,
pk,
sigma1,
patchEdges )
protected
Extend a template by the PSF to fill in the footprint.

Using the PSF, a footprint that touches the edge is passed to the
function and is grown by the psffwhm*1.5 and filled in with
ramped pixels.

Parameters
----------
log: `log.Log`
    LSST logger for logging purposes.
psffwhm: `float`
    PSF FWHM in pixels.
t1: `afw.image.ImageF`
    The image template that contains the footprint to extend.
tfoot: `afw.detection.Footprint`
    Symmetric Footprint to extend.
fp: `afw.detection.Footprint`
    Parent Footprint that is being deblended.
maskedImage: `afw.image.MaskedImageF`
    Full MaskedImage containing the parent footprint ``fp``.
x0,y0: `init`
    Minimum x,y for the bounding box of the footprint ``fp``.
x1,y1: `int`
    Maximum x,y for the bounding box of the footprint ``fp``.
psf: `afw.detection.Psf`
    PSF of the image.
pk: `afw.detection.PeakRecord`
    The peak within the Footprint whose footprint is being extended.
sigma1: `float`
    Estimated noise level in the image.
patchEdges: `bool`
    If ``patchEdges==True`` and if the footprint touches pixels with the
    ``EDGE`` bit set, then for spans whose symmetric mirror are outside
    the image, the symmetric footprint is grown to include them and their
    pixel values are stored.

Results
-------
t2: `afw.image.ImageF`
    Image of the extended footprint.
tfoot2: `afw.detection.Footprint`
    Extended Footprint.
patched: `bool`
    If the footprint touches an edge pixel, ``patched`` will be set to
    ``True``. Otherwise ``patched`` is ``False``.

Definition at line 825 of file plugins.py.

826 x0, x1, y0, y1, psf, pk, sigma1, patchEdges):
827 """Extend a template by the PSF to fill in the footprint.
828
829 Using the PSF, a footprint that touches the edge is passed to the
830 function and is grown by the psffwhm*1.5 and filled in with
831 ramped pixels.
832
833 Parameters
834 ----------
835 log: `log.Log`
836 LSST logger for logging purposes.
837 psffwhm: `float`
838 PSF FWHM in pixels.
839 t1: `afw.image.ImageF`
840 The image template that contains the footprint to extend.
841 tfoot: `afw.detection.Footprint`
842 Symmetric Footprint to extend.
843 fp: `afw.detection.Footprint`
844 Parent Footprint that is being deblended.
845 maskedImage: `afw.image.MaskedImageF`
846 Full MaskedImage containing the parent footprint ``fp``.
847 x0,y0: `init`
848 Minimum x,y for the bounding box of the footprint ``fp``.
849 x1,y1: `int`
850 Maximum x,y for the bounding box of the footprint ``fp``.
851 psf: `afw.detection.Psf`
852 PSF of the image.
853 pk: `afw.detection.PeakRecord`
854 The peak within the Footprint whose footprint is being extended.
855 sigma1: `float`
856 Estimated noise level in the image.
857 patchEdges: `bool`
858 If ``patchEdges==True`` and if the footprint touches pixels with the
859 ``EDGE`` bit set, then for spans whose symmetric mirror are outside
860 the image, the symmetric footprint is grown to include them and their
861 pixel values are stored.
862
863 Results
864 -------
865 t2: `afw.image.ImageF`
866 Image of the extended footprint.
867 tfoot2: `afw.detection.Footprint`
868 Extended Footprint.
869 patched: `bool`
870 If the footprint touches an edge pixel, ``patched`` will be set to
871 ``True``. Otherwise ``patched`` is ``False``.
872 """
873 log.trace('Found significant flux at template edge.')
874 # Compute the max of:
875 # -symmetric-template-clipped image * PSF
876 # -footprint-clipped image
877 # Ie, extend the template by the PSF and "fill in" the footprint.
878 # Then find the symmetric template of that image.
879
880 # The size we'll grow by
881 S = psffwhm*1.5
882 # make it an odd integer
883 S = int((S + 0.5)/2)*2 + 1
884
885 tbb = tfoot.getBBox()
886 tbb.grow(S)
887
888 # (footprint+margin)-clipped image;
889 # we need the pixels OUTSIDE the footprint to be 0.
890 fpcopy = afwDet.Footprint(fp)
891 fpcopy.dilate(S)
892 fpcopy.setSpans(fpcopy.spans.clippedTo(tbb))
893 fpcopy.removeOrphanPeaks()
894 padim = maskedImage.Factory(tbb)
895 fpcopy.spans.clippedTo(maskedImage.getBBox()).copyMaskedImage(maskedImage, padim)
896
897 # find pixels on the edge of the template
898 edgepix = bUtils.getSignificantEdgePixels(t1, tfoot, -1e6)
899
900 # instantiate PSF image
901 xc = int((x0 + x1)/2)
902 yc = int((y0 + y1)/2)
903 psfim = psf.computeImage(geom.Point2D(xc, yc))
904 pbb = psfim.getBBox()
905 # shift PSF image to be centered on zero
906 lx, ly = pbb.getMinX(), pbb.getMinY()
907 psfim.setXY0(lx - xc, ly - yc)
908 pbb = psfim.getBBox()
909 # clip PSF to S, if necessary
910 Sbox = geom.Box2I(geom.Point2I(-S, -S), geom.Extent2I(2*S+1, 2*S+1))
911 if not Sbox.contains(pbb):
912 # clip PSF image
913 psfim = psfim.Factory(psfim, Sbox, afwImage.PARENT, True)
914 pbb = psfim.getBBox()
915 px0 = pbb.getMinX()
916 px1 = pbb.getMaxX()
917 py0 = pbb.getMinY()
918 py1 = pbb.getMaxY()
919
920 # Compute the ramped-down edge pixels
921 ramped = t1.Factory(tbb)
922 Tout = ramped.getArray()
923 Tin = t1.getArray()
924 tx0, ty0 = t1.getX0(), t1.getY0()
925 ox0, oy0 = ramped.getX0(), ramped.getY0()
926 P = psfim.getArray()
927 P /= P.max()
928 # For each edge pixel, Tout = max(Tout, edgepix * PSF)
929 for span in edgepix.getSpans():
930 y = span.getY()
931 for x in range(span.getX0(), span.getX1()+1):
932 slc = (slice(y+py0 - oy0, y+py1+1 - oy0),
933 slice(x+px0 - ox0, x+px1+1 - ox0))
934 Tout[slc] = np.maximum(Tout[slc], Tin[y-ty0, x-tx0]*P)
935
936 # Fill in the "padim" (which has the right variance and
937 # mask planes) with the ramped pixels, outside the footprint
938 imZeros = (padim.getImage().getArray() == 0)
939 padim.getImage().getArray()[imZeros] = ramped.getArray()[imZeros]
940
941 t2, tfoot2, patched = bUtils.buildSymmetricTemplate(padim, fpcopy, pk, sigma1, True, patchEdges)
942
943 # This template footprint may extend outside the parent
944 # footprint -- or the image. Clip it.
945 # NOTE that this may make it asymmetric, unlike normal templates.
946 imbb = maskedImage.getBBox()
947 tfoot2.clipTo(imbb)
948 tbb = tfoot2.getBBox()
949 # clip template image to bbox
950 t2 = t2.Factory(t2, tbb, afwImage.PARENT, True)
951
952 return t2, tfoot2, patched
953
954

◆ _setPeakError()

lsst.meas.deblender.plugins._setPeakError ( debResult,
log,
pk,
cx,
cy,
filters,
msg,
flag )
protected
Update the peak in each band with an error

This function logs an error that occurs during deblending and sets the
relevant flag.

Parameters
----------
debResult: `lsst.meas.deblender.baseline.DeblenderResult`
    Container for the final deblender results.
log: `log.Log`
    LSST logger for logging purposes.
pk: int
    Number of the peak that failed
cx: float
    x coordinate of the peak
cy: float
    y coordinate of the peak
filters: list of str
    List of filter names for the exposures
msg: str
    Message to display in log traceback
flag: str
    Name of the flag to set

Returns
-------
None

Definition at line 129 of file plugins.py.

129def _setPeakError(debResult, log, pk, cx, cy, filters, msg, flag):
130 """Update the peak in each band with an error
131
132 This function logs an error that occurs during deblending and sets the
133 relevant flag.
134
135 Parameters
136 ----------
137 debResult: `lsst.meas.deblender.baseline.DeblenderResult`
138 Container for the final deblender results.
139 log: `log.Log`
140 LSST logger for logging purposes.
141 pk: int
142 Number of the peak that failed
143 cx: float
144 x coordinate of the peak
145 cy: float
146 y coordinate of the peak
147 filters: list of str
148 List of filter names for the exposures
149 msg: str
150 Message to display in log traceback
151 flag: str
152 Name of the flag to set
153
154 Returns
155 -------
156 None
157 """
158 log.trace("Peak %d at (%f,%f):%s", pk, cx, cy, msg)
159 for fidx, f in enumerate(filters):
160 pkResult = debResult.deblendedParents[f].peaks[pk]
161 getattr(pkResult, flag)()
162
163

◆ _weightTemplates()

lsst.meas.deblender.plugins._weightTemplates ( dp)
protected
Weight the templates to best match the parent Footprint in a single
filter

This includes weighting both regular templates and point source templates

Parameter
---------
dp: `DeblendedParent`
    The deblended parent to re-weight

Returns
-------
None

Definition at line 1105 of file plugins.py.

1105def _weightTemplates(dp):
1106 """Weight the templates to best match the parent Footprint in a single
1107 filter
1108
1109 This includes weighting both regular templates and point source templates
1110
1111 Parameter
1112 ---------
1113 dp: `DeblendedParent`
1114 The deblended parent to re-weight
1115
1116 Returns
1117 -------
1118 None
1119 """
1120 nchild = np.sum([pkres.skip is False for pkres in dp.peaks])
1121 A = np.zeros((dp.W*dp.H, nchild))
1122 parentImage = afwImage.ImageF(dp.bb)
1123 afwDet.copyWithinFootprintImage(dp.fp, dp.img, parentImage)
1124 b = parentImage.getArray().ravel()
1125
1126 index = 0
1127 for pkres in dp.peaks:
1128 if pkres.skip:
1129 continue
1130 childImage = afwImage.ImageF(dp.bb)
1131 afwDet.copyWithinFootprintImage(dp.fp, pkres.templateImage, childImage)
1132 A[:, index] = childImage.getArray().ravel()
1133 index += 1
1134
1135 X1, r1, rank1, s1 = np.linalg.lstsq(A, b, rcond=-1)
1136 del A
1137 del b
1138
1139 index = 0
1140 for pkres in dp.peaks:
1141 if pkres.skip:
1142 continue
1143 pkres.templateImage *= X1[index]
1144 pkres.setTemplateWeight(X1[index])
1145 index += 1
1146
1147

◆ apportionFlux()

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

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

◆ buildSymmetricTemplates()

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

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

◆ clipFootprintsToNonzero()

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

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

◆ clipFootprintToNonzeroImpl()

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.

38def 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()

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.

164def 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

◆ makeTemplatesMonotonic()

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

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

◆ medianSmoothTemplates()

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

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

◆ rampFluxAtEdge()

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

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

◆ reconstructTemplates()

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

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

◆ weightTemplates()

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

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