LSST Applications g0f08755f38+9c285cab97,g1635faa6d4+13f3999e92,g1653933729+a8ce1bb630,g1a0ca8cf93+bf6eb00ceb,g28da252d5a+0829b12dee,g29321ee8c0+5700dc9eac,g2bbee38e9b+9634bc57db,g2bc492864f+9634bc57db,g2cdde0e794+c2c89b37c4,g3156d2b45e+41e33cbcdc,g347aa1857d+9634bc57db,g35bb328faa+a8ce1bb630,g3a166c0a6a+9634bc57db,g3e281a1b8c+9f2c4e2fc3,g414038480c+077ccc18e7,g41af890bb2+fde0dd39b6,g5fbc88fb19+17cd334064,g781aacb6e4+a8ce1bb630,g80478fca09+55a9465950,g82479be7b0+d730eedb7d,g858d7b2824+9c285cab97,g9125e01d80+a8ce1bb630,g9726552aa6+10f999ec6a,ga5288a1d22+2a84bb7594,gacf8899fa4+c69c5206e8,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+9634bc57db,gcf0d15dbbd+4b7d09cae4,gda3e153d99+9c285cab97,gda6a2b7d83+4b7d09cae4,gdaeeff99f8+1711a396fd,ge2409df99d+5e831397f4,ge79ae78c31+9634bc57db,gf0baf85859+147a0692ba,gf3967379c6+41c94011de,gf3fb38a9a8+8f07a9901b,gfb92a5be7c+9c285cab97,w.2024.46
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 240 of file plugins.py.

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

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

◆ _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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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 130 of file plugins.py.

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

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

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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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()

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 : `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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.

708def 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 : `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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.

1041def 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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 165 of file plugins.py.

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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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.

1005def 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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.

956def 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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.

770def 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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:
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

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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.

1149def 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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...

◆ 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
    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.

1077def 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: `lsst.log.Logger` or `lsst.utils.logging.LsstLogAdapter`
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