33 from .baselineUtils
import BaselineUtilsF
as bUtils
38 Clips the given *Footprint* to the region in the *Image* 39 containing non-zero values. The clipping drops spans that are 40 totally zero, and moves endpoints to non-zero; it does not 41 split spans that have internal zeros. 45 xImMax = x0 + image.getDimensions().getX()
46 yImMax = y0 + image.getDimensions().getY()
48 arr = image.getArray()
49 for span
in foot.spans:
51 if y < y0
or y > yImMax:
55 xMin = spanX0
if spanX0 >= x0
else x0
56 xMax = spanX1
if spanX1 <= xImMax
else xImMax
57 xarray = np.arange(xMin, xMax+1)[arr[y-y0, xMin-x0:xMax-x0+1] != 0]
62 foot.removeOrphanPeaks()
66 """Class to define plugins for the deblender. 68 The new deblender executes a series of plugins specified by the user. 69 Each plugin defines the function to be executed, the keyword arguments required by the function, 70 and whether or not certain portions of the deblender might need to be rerun as a result of 73 def __init__(self, func, onReset=None, maxIterations=50, **kwargs):
74 """Initialize a deblender plugin 79 Function to run when the plugin is executed. The function should always take 80 `debResult`, a `DeblenderResult` that stores the deblender result, and 81 `log`, an `lsst.log`, as the first two arguments, as well as any additional 82 keyword arguments (that must be specified in ``kwargs``). 83 The function should also return ``modified``, a `bool` that tells the deblender whether 84 or not any templates have been modified by the function. 85 If ``modified==True``, the deblender will go back to step ``onReset``, 86 unless the has already been run ``maxIterations``. 88 Index of the deblender plugin to return to if ``func`` modifies any templates. 89 The default is ``None``, which does not re-run any plugins. 91 Maximum number of times the deblender will reset when the current plugin 101 def run(self, debResult, log):
102 """Execute the current plugin 104 Once the plugin has finished, check to see if part of the deblender must be executed again. 106 log.trace(
"Executing %s", self.
func.__name__)
107 reset = self.
func(debResult, log, **self.
kwargs)
115 return (
"<Deblender Plugin: func={0}, kwargs={1}".format(self.
func.__name__, self.
kwargs))
121 def _setPeakError(debResult, log, pk, cx, cy, filters, msg, flag):
122 """Update the peak in each band with an error 124 This function logs an error that occurs during deblending and sets the 129 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 130 Container for the final deblender results. 132 LSST logger for logging purposes. 134 Number of the peak that failed 136 x coordinate of the peak 138 y coordinate of the peak 140 List of filter names for the exposures 142 Message to display in log traceback 144 Name of the flag to set 150 log.trace(
"Peak {0} at ({1},{2}):{3}".format(pk, cx, cy, msg))
151 for fidx, f
in enumerate(filters):
152 pkResult = debResult.deblendedParents[f].peaks[pk]
153 getattr(pkResult, flag)()
157 sources=None, constraints=None, config=None, maxIter=100, bgScale=0.5,
158 relativeError=1e-2, badMask=None):
159 """Run the Multiband Deblender to build templates 163 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 164 Container for the final deblender results. 166 LSST logger for logging purposes. 167 useWeights: bool, default=False 168 Whether or not to use the variance map in each filter for the fit. 169 usePsf: bool, default=False 170 Whether or not to convolve the image with the PSF in each band. 171 This is not yet implemented in an optimized algorithm, so it is recommended 172 to leave this term off for now 173 sources: list of `scarlet.source.Source` objects, default=None 174 List of sources to use in the blend. By default the 175 `scarlet.source.ExtendedSource` class is used, which initializes each 176 source as symmetric and monotonic about a peak in the footprint peak catalog. 177 constraints: `scarlet.constraint.Constraint`, default=None 178 Constraint to be applied to each source. If sources require different constraints, 179 a list of `sources` must be created instead, which ignores the `constraints` parameter. 180 When `constraints` is `None` the default constraints are used. 181 config: `scarlet.config.Config`, default=None 182 Configuration for the blend. 183 If `config` is `None` then the default `Config` is used. 184 maxIter: int, default=100 185 Maximum iterations for a single blend. 187 Amount to scale the background RMS to set the floor for deblender model sizes 188 relativeError: float, default=1e-2 189 Relative error to reach for convergence 190 badMask: list of str, default=`None` 191 List of mask plane names to mark bad pixels. 192 If `badPixelKeys` is `None`, the default keywords used are 193 `["BAD", "CR", "NO_DATA", "SAT", "SUSPECT"]`. 198 If any templates have been created then ``modified`` is ``True``, 199 otherwise it is ``False`` (meaning all of the peaks were skipped). 202 bbox = debResult.footprint.getBBox()
203 peakSchema = debResult.footprint.peaks.getSchema()
204 xmin = bbox.getMinX()
205 ymin = bbox.getMinY()
206 peaks = [[pk.y-ymin, pk.x-xmin]
for pk
in debResult.peaks]
210 mMaskedImage = debResult.mMaskedImage[:, debResult.footprint.getBBox()]
211 data = mMaskedImage.image.array
215 weights = 1/mMaskedImage.variance.array
217 weights = np.ones_like(data)
222 badMask = [
"BAD",
"CR",
"NO_DATA",
"SAT",
"SUSPECT"]
223 fpMask = afwImage.Mask(bbox)
224 debResult.footprint.spans.setMask(fpMask, 1)
225 fpMask = ~fpMask.getArray().astype(bool)
226 badPixels = mMaskedImage.mask.getPlaneBitMask(badMask)
227 mask = (mMaskedImage.mask.array & badPixels) | fpMask[
None, :]
228 weights[mask > 0] = 0
233 for psf
in debResult.psfs:
234 psfs.append(psf.computeKernelImage().array)
239 bg_rms = np.array([debResult.deblendedParents[f].avgNoise
for f
in debResult.filters])*bgScale
242 if constraints
is None or isinstance(constraints[0], scarlet.constraints.Constraint):
243 constraints = [constraints] * len(peaks)
245 scarlet.source.ExtendedSource(center=peak,
248 constraints=constraints[pk],
254 for pk, peak
in enumerate(peaks)
261 blend = scarlet.blend.Blend(components=sources)
262 blend.set_data(img=data, weights=weights, bg_rms=bg_rms, config=config)
263 blend.fit(maxIter, e_rel=relativeError)
264 except scarlet.source.SourceInitError
as e:
266 debResult.failed =
True 268 except np.linalg.LinAlgError:
269 log.warn(
"Deblend failed catastrophically, most likely due to no signal in the footprint")
270 debResult.failed =
True 272 debResult.blend = blend
276 for pk, source
in enumerate(blend.sources):
277 src = source.components[0]
281 if debResult.peaks[pk].skip:
284 cx = src.center[1]+xmin
285 cy = src.center[0]+ymin
286 icx = int(np.round(cx))
287 icy = int(np.round(cy))
288 imbb = debResult.deblendedParents[debResult.filters[0]].img.getBBox()
292 _setPeakError(debResult, log, pk, cx, cy, debResult.filters,
293 "peak center is not inside image",
"setOutOfBounds")
296 if np.sum(src.morph) == 0:
297 _setPeakError(debResult, log, pk, cx, cy, debResult.filters,
298 "had no flux",
"setFailedSymmetricTemplate")
302 model = blend.get_model(k=pk).astype(np.float32)
305 mask = afwImage.Mask(np.array(np.sum(model, axis=0) > 0, dtype=np.int32), xy0=xy0)
306 ss = afwGeom.SpanSet.fromMask(mask)
309 log.warn(
"No flux in parent footprint")
310 debResult.failed =
True 314 for fidx, f
in enumerate(debResult.filters):
315 pkResult = debResult.deblendedParents[f].peaks[pk]
316 tfoot = afwDet.Footprint(ss, peakSchema=peakSchema)
319 peakFlux = np.sum(src.sed[fidx]*src.morph[_cy, _cx])
320 tfoot.addPeak(cx, cy, peakFlux)
321 timg = afwImage.ImageF(model[fidx], xy0=xy0)
322 timg = timg[tfoot.getBBox()]
323 pkResult.setOrigTemplate(timg, tfoot)
324 pkResult.setTemplate(timg, tfoot)
325 pkResult.setFluxPortion(afwImage.MaskedImageF(timg))
326 pkResult.multiColorPeak.x = cx
327 pkResult.multiColorPeak.y = cy
328 pkResult.peak.setFx(cx)
329 pkResult.peak.setFy(cy)
330 pkResult.peak.setIx(icx)
331 pkResult.peak.setIy(icy)
335 def fitPsfs(debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2):
336 """Fit a PSF + smooth background model (linear) to a small region around each peak 338 This function will iterate over all filters in deblender result but does not compare 339 results across filters. 340 DeblendedPeaks that pass the cuts have their templates modified to the PSF + background model 341 and their ``deblendedAsPsf`` property set to ``True``. 343 This will likely be replaced in the future with a function that compares the psf chi-squared cuts 344 so that peaks flagged as point sources will be considered point sources in all bands. 348 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 349 Container for the final deblender results. 351 LSST logger for logging purposes. 352 psfChisqCut*: `float`, optional 353 ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to 354 be considered a PSF match without recentering. 355 A fit is also made that includes terms to recenter the PSF. 356 ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the 357 fit that includes recentering terms. 358 If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and 359 the peak footprint is fit again, this time to the new PSF. 360 If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it 361 passes the re-centering algorithm. 362 If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted, 363 but parameters for all three psf fits are stored in the ``DebldendedPeak``. 364 The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``. 365 tinyFootprintSize: `float`, optional 366 The PSF model is shrunk to the size that contains the original footprint. 367 If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)`` 368 then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit. 374 If any templates have been assigned to PSF point sources then ``modified`` is ``True``, 375 otherwise it is ``False``. 377 from .baseline
import CachingPsf
380 for fidx
in debResult.filters:
381 dp = debResult.deblendedParents[fidx]
382 peaks = dp.fp.getPeaks()
386 fmask = afwImage.Mask(dp.bb)
387 fmask.setXY0(dp.bb.getMinX(), dp.bb.getMinY())
388 dp.fp.spans.setMask(fmask, 1)
393 peakF = [pk.getF()
for pk
in peaks]
395 for pki, (pk, pkres, pkF)
in enumerate(zip(peaks, dp.peaks, peakF)):
396 log.trace(
'Filter %s, Peak %i', fidx, pki)
397 ispsf = _fitPsf(dp.fp, fmask, pk, pkF, pkres, dp.bb, peaks, peakF, log, cpsf, dp.psffwhm,
398 dp.img, dp.varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b, tinyFootprintSize)
399 modified = modified
or ispsf
403 def _fitPsf(fp, fmask, pk, pkF, pkres, fbb, peaks, peaksF, log, psf, psffwhm,
404 img, varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b,
407 """Fit a PSF + smooth background model (linear) to a small region around a peak. 409 See fitPsfs for a more thorough description, including all parameters not described below. 413 fp: `afw.detection.Footprint` 414 Footprint containing the Peaks to model. 415 fmask: `afw.image.Mask` 416 The Mask plane for pixels in the Footprint 417 pk: `afw.detection.PeakRecord` 418 The peak within the Footprint that we are going to fit with PSF model 419 pkF: `afw.geom.Point2D` 420 Floating point coordinates of the peak. 421 pkres: `meas.deblender.DeblendedPeak` 422 Peak results object that will hold the results. 423 fbb: `afw.geom.Box2I` 424 Bounding box of ``fp`` 425 peaks: `afw.detection.PeakCatalog` 426 Catalog of peaks contained in the parent footprint. 427 peaksF: list of `afw.geom.Point2D` 428 List of floating point coordinates of all of the peaks. 429 psf: list of `afw.detection.Psf`s 430 Psf of the ``maskedImage`` for each band. 431 psffwhm: list pf `float`s 432 FWHM of the ``maskedImage``'s ``psf`` in each band. 433 img: `afw.image.ImageF` 434 The image that contains the footprint. 435 varimg: `afw.image.ImageF` 436 The variance of the image that contains the footprint. 441 Whether or not the peak matches a PSF model. 451 R0 = int(np.ceil(psffwhm*1.))
453 R1 = int(np.ceil(psffwhm*1.5))
454 cx, cy = pkF.getX(), pkF.getY()
455 psfimg = psf.computeImage(cx, cy)
457 R2 = R1 + min(psfimg.getWidth(), psfimg.getHeight())/2.
459 pbb = psfimg.getBBox()
461 px0, py0 = psfimg.getX0(), psfimg.getY0()
466 pkres.setOutOfBounds()
470 xlo = int(np.floor(cx - R1))
471 ylo = int(np.floor(cy - R1))
472 xhi = int(np.ceil(cx + R1))
473 yhi = int(np.ceil(cy + R1))
476 xlo, xhi = stampbb.getMinX(), stampbb.getMaxX()
477 ylo, yhi = stampbb.getMinY(), stampbb.getMaxY()
478 if xlo > xhi
or ylo > yhi:
479 log.trace(
'Skipping this peak: out of bounds')
480 pkres.setOutOfBounds()
484 if min(stampbb.getWidth(), stampbb.getHeight()) <= max(tinyFootprintSize, 2):
487 log.trace(
'Skipping this peak: tiny footprint / close to edge')
488 pkres.setTinyFootprint()
493 for pk2, pkF2
in zip(peaks, peaksF):
496 if pkF.distanceSquared(pkF2) > R2**2:
498 opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
499 if not opsfimg.getBBox().overlaps(stampbb):
501 otherpeaks.append(opsfimg)
502 log.trace(
'%i other peaks within range', len(otherpeaks))
508 NT1 = 4 + len(otherpeaks)
512 NP = (1 + yhi - ylo)*(1 + xhi - xlo)
524 ix0, iy0 = img.getX0(), img.getY0()
525 fx0, fy0 = fbb.getMinX(), fbb.getMinY()
526 fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
527 islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
528 fmask_sub = fmask .getArray()[fslice]
529 var_sub = varimg.getArray()[islice]
530 img_sub = img.getArray()[islice]
533 psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
534 pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
535 px0, px1 = pbb.getMinX(), pbb.getMaxX()
536 py0, py1 = pbb.getMinY(), pbb.getMaxY()
539 valid = (fmask_sub > 0)
540 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
541 RR = ((xx - cx)**2)[np.newaxis, :] + ((yy - cy)**2)[:, np.newaxis]
542 valid *= (RR <= R1**2)
543 valid *= (var_sub > 0)
547 log.warn(
'Skipping peak at (%.1f, %.1f): no unmasked pixels nearby', cx, cy)
548 pkres.setNoValidPixels()
552 XX, YY = np.meshgrid(xx, yy)
553 ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
555 inpsfx = (xx >= px0)*(xx <= px1)
556 inpsfy = (yy >= py0)*(yy <= py1)
557 inpsf = np.outer(inpsfy, inpsfx)
558 indx = np.outer(inpsfy, (xx > px0)*(xx < px1))
559 indy = np.outer((yy > py0)*(yy < py1), inpsfx)
564 def _overlap(xlo, xhi, xmin, xmax):
565 assert((xlo <= xmax)
and (xhi >= xmin)
and 566 (xlo <= xhi)
and (xmin <= xmax))
567 xloclamp = max(xlo, xmin)
569 xhiclamp = min(xhi, xmax)
570 Xhi = Xlo + (xhiclamp - xloclamp)
571 assert(xloclamp >= 0)
573 return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
575 A = np.zeros((NP, NT2))
579 A[:, I_sky_ramp_x] = ipixes[:, 0] + (xlo-cx)
580 A[:, I_sky_ramp_y] = ipixes[:, 1] + (ylo-cy)
583 px0, px1 = pbb.getMinX(), pbb.getMaxX()
584 py0, py1 = pbb.getMinY(), pbb.getMaxY()
585 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
586 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
587 dpx0, dpy0 = px0 - xlo, py0 - ylo
588 psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
589 psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
590 psfsub = psfarr[psf_y_slice, psf_x_slice]
591 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
592 A[inpsf[valid], I_psf] = psfsub[vsub]
596 oldsx = (sx1, sx2, sx3, sx4)
597 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0+1, px1-1)
598 psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1] -
599 psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1])/2.
600 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
601 A[indx[valid], I_dx] = psfsub[vsub]
603 (sx1, sx2, sx3, sx4) = oldsx
606 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0+1, py1-1)
607 psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice] -
608 psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice])/2.
609 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
610 A[indy[valid], I_dy] = psfsub[vsub]
613 for j, opsf
in enumerate(otherpeaks):
615 ino = np.outer((yy >= obb.getMinY())*(yy <= obb.getMaxY()),
616 (xx >= obb.getMinX())*(xx <= obb.getMaxX()))
617 dpx0, dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
618 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
619 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
620 opsfarr = opsf.getArray()
621 psfsub = opsfarr[sy3 - dpy0: sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
622 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
623 A[ino[valid], I_opsf + j] = psfsub[vsub]
629 rw = np.ones_like(RR)
632 rw[ii] = np.maximum(0, 1. - ((rr - R0)/(R1 - R0)))
633 w = np.sqrt(rw[valid]/var_sub[valid])
635 sumr = np.sum(rw[valid])
636 log.debug(
'sumr = %g', sumr)
640 Aw = A*w[:, np.newaxis]
649 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
650 im1[ipixes[:, 1], ipixes[:, 0]] = A[:, i]
651 plt.subplot(R, C, i+1)
652 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
653 plt.subplot(R, C, NT2+1)
654 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
655 im1[ipixes[:, 1], ipixes[:, 0]] = b
656 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
657 plt.subplot(R, C, NT2+2)
658 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
659 im1[ipixes[:, 1], ipixes[:, 0]] = w
660 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
672 X1, r1, rank1, s1 = np.linalg.lstsq(Aw[:, :NT1], bw, rcond=-1)
674 X2, r2, rank2, s2 = np.linalg.lstsq(Aw, bw, rcond=-1)
675 except np.linalg.LinAlgError
as e:
676 log.warn(
"Failed to fit PSF to child: %s", e)
677 pkres.setPsfFitFailed()
680 log.debug(
'r1 r2 %s %s', r1, r2)
692 dof1 = sumr - len(X1)
693 dof2 = sumr - len(X2)
694 log.debug(
'dof1, dof2 %g %g', dof1, dof2)
697 if dof1 <= 0
or dof2 <= 0:
698 log.trace(
'Skipping this peak: bad DOF %g, %g', dof1, dof2)
704 log.trace(
'PSF fits: chisq/dof = %g, %g', q1, q2)
705 ispsf1 = (q1 < psfChisqCut1)
706 ispsf2 = (q2 < psfChisqCut2)
708 pkres.psfFit1 = (chisq1, dof1)
709 pkres.psfFit2 = (chisq2, dof2)
713 fdx, fdy = X2[I_dx], X2[I_dy]
718 ispsf2 = ispsf2
and (abs(dx) < 1.
and abs(dy) < 1.)
719 log.trace(
'isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s', dx, dy, str(ispsf2))
721 pkres.psfFitBigDecenter =
True 726 psfimg2 = psf.computeImage(cx + dx, cy + dy)
728 pbb2 = psfimg2.getBBox()
733 if not pbb2.contains(
geom.Point2I(int(cx + dx), int(cy + dy))):
737 px0, py0 = psfimg2.getX0(), psfimg2.getY0()
738 psfarr = psfimg2.getArray()[pbb2.getMinY()-py0:1+pbb2.getMaxY()-py0,
739 pbb2.getMinX()-px0:1+pbb2.getMaxX()-px0]
740 px0, py0 = pbb2.getMinX(), pbb2.getMinY()
741 px1, py1 = pbb2.getMaxX(), pbb2.getMaxY()
746 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
747 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
748 dpx0, dpy0 = px0 - xlo, py0 - ylo
749 psfsub = psfarr[sy3-dpy0:sy4-dpy0, sx3-dpx0:sx4-dpx0]
750 vsub = valid[sy1-ylo:sy2-ylo, sx1-xlo:sx2-xlo]
751 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
752 inpsf = np.outer((yy >= py0)*(yy <= py1), (xx >= px0)*(xx <= px1))
753 Ab[inpsf[valid], I_psf] = psfsub[vsub]
755 Aw = Ab*w[:, np.newaxis]
757 Xb, rb, rankb, sb = np.linalg.lstsq(Aw, bw, rcond=-1)
762 dofb = sumr - len(Xb)
764 ispsf2 = (qb < psfChisqCut2b)
767 log.trace(
'shifted PSF: new chisq/dof = %g; good? %s', qb, ispsf2)
768 pkres.psfFit3 = (chisqb, dofb)
771 if (((ispsf1
and ispsf2)
and (q2 < q1))
or 772 (ispsf2
and not ispsf1)):
776 log.debug(
'dof %g', dof)
777 log.trace(
'Keeping shifted-PSF model')
780 pkres.psfFitWithDecenter =
True 786 log.debug(
'dof %g', dof)
787 log.trace(
'Keeping unshifted PSF model')
789 ispsf = (ispsf1
or ispsf2)
793 SW, SH = 1+xhi-xlo, 1+yhi-ylo
794 psfmod = afwImage.ImageF(SW, SH)
795 psfmod.setXY0(xlo, ylo)
796 psfderivmodm = afwImage.MaskedImageF(SW, SH)
797 psfderivmod = psfderivmodm.getImage()
798 psfderivmod.setXY0(xlo, ylo)
799 model = afwImage.ImageF(SW, SH)
800 model.setXY0(xlo, ylo)
801 for i
in range(len(Xpsf)):
802 for (x, y), v
in zip(ipixes, A[:, i]*Xpsf[i]):
803 ix, iy = int(x), int(y)
804 model.set(ix, iy, model.get(ix, iy) + float(v))
805 if i
in [I_psf, I_dx, I_dy]:
806 psfderivmod.set(ix, iy, psfderivmod.get(ix, iy) + float(v))
809 psfmod.set(int(x), int(y), float(A[ii, I_psf]*Xpsf[I_psf]))
810 modelfp = afwDet.Footprint(fp.getPeaks().getSchema())
811 for (x, y)
in ipixes:
812 modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
815 pkres.psfFitDebugPsf0Img = psfimg
816 pkres.psfFitDebugPsfImg = psfmod
817 pkres.psfFitDebugPsfDerivImg = psfderivmod
818 pkres.psfFitDebugPsfModel = model
819 pkres.psfFitDebugStamp = img.Factory(img, stampbb,
True)
820 pkres.psfFitDebugValidPix = valid
821 pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb,
True)
822 ww = np.zeros(valid.shape, np.float)
824 pkres.psfFitDebugWeight = ww
825 pkres.psfFitDebugRampWeight = rw
830 pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
831 pkres.psfFitCenter = (cx, cy)
832 log.debug(
'saving chisq,dof %g %g', chisq, dof)
833 pkres.psfFitBest = (chisq, dof)
834 pkres.psfFitParams = Xpsf
835 pkres.psfFitFlux = Xpsf[I_psf]
836 pkres.psfFitNOthers = len(otherpeaks)
839 pkres.setDeblendedAsPsf()
843 log.trace(
'Deblending as PSF; setting template to PSF model')
846 psfimg = psf.computeImage(cx, cy)
848 psfimg *= Xpsf[I_psf]
849 psfimg = psfimg.convertF()
852 fpcopy = afwDet.Footprint(fp)
853 psfbb = psfimg.getBBox()
855 bb = fpcopy.getBBox()
858 psfmod = afwImage.ImageF(bb)
859 fpcopy.spans.copyImage(psfimg, psfmod)
862 pkres.setTemplate(psfmod, fpcopy)
865 pkres.setPsfTemplate(psfmod, fpcopy)
871 """Build a symmetric template for each peak in each filter 873 Given ``maskedImageF``, ``footprint``, and a ``DebldendedPeak``, creates a symmetric template 874 (``templateImage`` and ``templateFootprint``) around the peak for all peaks not flagged as 875 ``skip`` or ``deblendedAsPsf``. 879 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 880 Container for the final deblender results. 882 LSST logger for logging purposes. 883 patchEdges: `bool`, optional 884 If True and if the parent Footprint touches pixels with the ``EDGE`` bit set, 885 then grow the parent Footprint to include all symmetric templates. 890 If any peaks are not skipped or marked as point sources, ``modified`` is ``True. 891 Otherwise ``modified`` is ``False``. 895 for fidx
in debResult.filters:
896 dp = debResult.deblendedParents[fidx]
897 imbb = dp.img.getBBox()
898 log.trace(
'Creating templates for footprint at x0,y0,W,H = %i, %i, %i, %i)', dp.x0, dp.y0, dp.W, dp.H)
900 for peaki, pkres
in enumerate(dp.peaks):
901 log.trace(
'Deblending peak %i of %i', peaki, len(dp.peaks))
904 if pkres.skip
or pkres.deblendedAsPsf:
908 cx, cy = pk.getIx(), pk.getIy()
910 log.trace(
'Peak center is not inside image; skipping %i', pkres.pki)
911 pkres.setOutOfBounds()
913 log.trace(
'computing template for peak %i at (%i, %i)', pkres.pki, cx, cy)
914 timg, tfoot, patched = bUtils.buildSymmetricTemplate(dp.maskedImage, dp.fp, pk, dp.avgNoise,
917 log.trace(
'Peak %i at (%i, %i): failed to build symmetric template', pkres.pki, cx, cy)
918 pkres.setFailedSymmetricTemplate()
926 pkres.setOrigTemplate(timg, tfoot)
927 pkres.setTemplate(timg, tfoot)
932 """Adjust flux on the edges of the template footprints. 934 Using the PSF, a peak ``Footprint`` with pixels on the edge of ``footprint`` 935 is grown by the ``psffwhm``*1.5 and filled in with ramped pixels. 936 The result is a new symmetric footprint template for the peaks near the edge. 940 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 941 Container for the final deblender results. 943 LSST logger for logging purposes. 944 patchEdges: `bool`, optional 945 If True and if the parent Footprint touches pixels with the ``EDGE`` bit set, 946 then grow the parent Footprint to include all symmetric templates. 951 If any peaks have their templates modified to include flux at the edges, 952 ``modified`` is ``True``. 956 for fidx
in debResult.filters:
957 dp = debResult.deblendedParents[fidx]
958 log.trace(
'Checking for significant flux at edge: sigma1=%g', dp.avgNoise)
960 for peaki, pkres
in enumerate(dp.peaks):
961 if pkres.skip
or pkres.deblendedAsPsf:
963 timg, tfoot = pkres.templateImage, pkres.templateFootprint
964 if bUtils.hasSignificantFluxAtEdge(timg, tfoot, 3*dp.avgNoise):
965 log.trace(
"Template %i has significant flux at edge: ramping", pkres.pki)
967 (timg2, tfoot2, patched) = _handle_flux_at_edge(log, dp.psffwhm, timg, tfoot, dp.fp,
968 dp.maskedImage, dp.x0, dp.x1,
969 dp.y0, dp.y1, dp.psf, pkres.peak,
970 dp.avgNoise, patchEdges)
973 "CoaddPsf" in str(exc)):
974 pkres.setOutOfBounds()
977 pkres.setRampedTemplate(timg2, tfoot2)
980 pkres.setTemplate(timg2, tfoot2)
985 def _handle_flux_at_edge(log, psffwhm, t1, tfoot, fp, maskedImage,
986 x0, x1, y0, y1, psf, pk, sigma1, patchEdges):
987 """Extend a template by the PSF to fill in the footprint. 989 Using the PSF, a footprint that touches the edge is passed to the function 990 and is grown by the psffwhm*1.5 and filled in with ramped pixels. 995 LSST logger for logging purposes. 998 t1: `afw.image.ImageF` 999 The image template that contains the footprint to extend. 1000 tfoot: `afw.detection.Footprint` 1001 Symmetric Footprint to extend. 1002 fp: `afw.detection.Footprint` 1003 Parent Footprint that is being deblended. 1004 maskedImage: `afw.image.MaskedImageF` 1005 Full MaskedImage containing the parent footprint ``fp``. 1007 Minimum x,y for the bounding box of the footprint ``fp``. 1009 Maximum x,y for the bounding box of the footprint ``fp``. 1010 psf: `afw.detection.Psf` 1012 pk: `afw.detection.PeakRecord` 1013 The peak within the Footprint whose footprint is being extended. 1015 Estimated noise level in the image. 1017 If ``patchEdges==True`` and if the footprint touches pixels with the 1018 ``EDGE`` bit set, then for spans whose symmetric mirror are outside the 1019 image, the symmetric footprint is grown to include them and their 1020 pixel values are stored. 1024 t2: `afw.image.ImageF` 1025 Image of the extended footprint. 1026 tfoot2: `afw.detection.Footprint` 1029 If the footprint touches an edge pixel, ``patched`` will be set to ``True``. 1030 Otherwise ``patched`` is ``False``. 1032 log.trace(
'Found significant flux at template edge.')
1042 S = int((S + 0.5)/2)*2 + 1
1044 tbb = tfoot.getBBox()
1049 fpcopy = afwDet.Footprint(fp)
1051 fpcopy.setSpans(fpcopy.spans.clippedTo(tbb))
1052 fpcopy.removeOrphanPeaks()
1053 padim = maskedImage.Factory(tbb)
1054 fpcopy.spans.clippedTo(maskedImage.getBBox()).copyMaskedImage(maskedImage, padim)
1057 edgepix = bUtils.getSignificantEdgePixels(t1, tfoot, -1e6)
1060 xc = int((x0 + x1)/2)
1061 yc = int((y0 + y1)/2)
1063 pbb = psfim.getBBox()
1065 lx, ly = pbb.getMinX(), pbb.getMinY()
1066 psfim.setXY0(lx - xc, ly - yc)
1067 pbb = psfim.getBBox()
1070 if not Sbox.contains(pbb):
1072 psfim = psfim.Factory(psfim, Sbox, afwImage.PARENT,
True)
1073 pbb = psfim.getBBox()
1080 ramped = t1.Factory(tbb)
1081 Tout = ramped.getArray()
1083 tx0, ty0 = t1.getX0(), t1.getY0()
1084 ox0, oy0 = ramped.getX0(), ramped.getY0()
1085 P = psfim.getArray()
1088 for span
in edgepix.getSpans():
1090 for x
in range(span.getX0(), span.getX1()+1):
1091 slc = (slice(y+py0 - oy0, y+py1+1 - oy0),
1092 slice(x+px0 - ox0, x+px1+1 - ox0))
1093 Tout[slc] = np.maximum(Tout[slc], Tin[y-ty0, x-tx0]*P)
1097 imZeros = (padim.getImage().getArray() == 0)
1098 padim.getImage().getArray()[imZeros] = ramped.getArray()[imZeros]
1100 t2, tfoot2, patched = bUtils.buildSymmetricTemplate(padim, fpcopy, pk, sigma1,
True, patchEdges)
1105 imbb = maskedImage.getBBox()
1107 tbb = tfoot2.getBBox()
1109 t2 = t2.Factory(t2, tbb, afwImage.PARENT,
True)
1111 return t2, tfoot2, patched
1115 """Applying median smoothing filter to the template images for every peak in every filter. 1119 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1120 Container for the final deblender results. 1122 LSST logger for logging purposes. 1123 medianFilterHalfSize: `int`, optional 1124 Half the box size of the median filter, i.e. a ``medianFilterHalfSize`` of 50 means that 1125 each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image. 1126 This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored. 1131 Whether or not any templates were modified. 1132 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1136 for fidx
in debResult.filters:
1137 dp = debResult.deblendedParents[fidx]
1138 for peaki, pkres
in enumerate(dp.peaks):
1139 if pkres.skip
or pkres.deblendedAsPsf:
1142 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1143 filtsize = medianFilterHalfsize*2 + 1
1144 if timg.getWidth() >= filtsize
and timg.getHeight() >= filtsize:
1145 log.trace(
'Median filtering template %i', pkres.pki)
1148 inimg = timg.Factory(timg,
True)
1149 bUtils.medianFilter(inimg, timg, medianFilterHalfsize)
1151 pkres.setMedianFilteredTemplate(timg, tfoot)
1153 log.trace(
'Not median-filtering template %i: size %i x %i smaller than required %i x %i',
1154 pkres.pki, timg.getWidth(), timg.getHeight(), filtsize, filtsize)
1155 pkres.setTemplate(timg, tfoot)
1160 """Make the templates monotonic. 1162 The pixels in the templates are modified such that pixels further from the peak will 1163 have values smaller than those closer to the peak. 1167 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1168 Container for the final deblender results. 1170 LSST logger for logging purposes. 1175 Whether or not any templates were modified. 1176 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1180 for fidx
in debResult.filters:
1181 dp = debResult.deblendedParents[fidx]
1182 for peaki, pkres
in enumerate(dp.peaks):
1183 if pkres.skip
or pkres.deblendedAsPsf:
1186 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1188 log.trace(
'Making template %i monotonic', pkres.pki)
1189 bUtils.makeMonotonic(timg, pk)
1190 pkres.setTemplate(timg, tfoot)
1195 """Clip non-zero spans in the template footprints for every peak in each filter. 1197 Peak ``Footprint``s are clipped to the region in the image containing non-zero values 1198 by dropping spans that are completely zero and moving endpoints to non-zero pixels 1199 (but does not split spans that have internal zeros). 1203 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1204 Container for the final deblender results. 1206 LSST logger for logging purposes. 1211 Whether or not any templates were modified. 1212 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1215 for fidx
in debResult.filters:
1216 dp = debResult.deblendedParents[fidx]
1217 for peaki, pkres
in enumerate(dp.peaks):
1218 if pkres.skip
or pkres.deblendedAsPsf:
1220 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1222 if not tfoot.getBBox().isEmpty()
and tfoot.getBBox() != timg.getBBox(afwImage.PARENT):
1223 timg = timg.Factory(timg, tfoot.getBBox(), afwImage.PARENT,
True)
1224 pkres.setTemplate(timg, tfoot)
1229 """Weight the templates to best fit the observed image in each filter 1231 This function re-weights the templates so that their linear combination best represents 1232 the observed image in that filter. 1233 In the future it may be useful to simultaneously weight all of the filters together. 1237 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1238 Container for the final deblender results. 1240 LSST logger for logging purposes. 1245 ``weightTemplates`` does not actually modify the ``Footprint`` templates other than 1246 to add a weight to them, so ``modified`` is always ``False``. 1249 log.trace(
'Weighting templates')
1250 for fidx
in debResult.filters:
1251 _weightTemplates(debResult.deblendedParents[fidx])
1255 def _weightTemplates(dp):
1256 """Weight the templates to best match the parent Footprint in a single filter 1258 This includes weighting both regular templates and point source templates 1262 dp: `DeblendedParent` 1263 The deblended parent to re-weight 1269 nchild = np.sum([pkres.skip
is False for pkres
in dp.peaks])
1270 A = np.zeros((dp.W*dp.H, nchild))
1271 parentImage = afwImage.ImageF(dp.bb)
1272 afwDet.copyWithinFootprintImage(dp.fp, dp.img, parentImage)
1273 b = parentImage.getArray().ravel()
1276 for pkres
in dp.peaks:
1279 childImage = afwImage.ImageF(dp.bb)
1280 afwDet.copyWithinFootprintImage(dp.fp, pkres.templateImage, childImage)
1281 A[:, index] = childImage.getArray().ravel()
1284 X1, r1, rank1, s1 = np.linalg.lstsq(A, b, rcond=-1)
1289 for pkres
in dp.peaks:
1292 pkres.templateImage *= X1[index]
1293 pkres.setTemplateWeight(X1[index])
1298 """Remove "degenerate templates" 1300 If galaxies have substructure, such as face-on spirals, the process of identifying peaks can 1301 "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite 1302 similar because they represent the same galaxy, so we try to identify these "degenerate" peaks 1303 by looking at the inner product (in pixel space) of pairs of templates. 1304 If they are nearly parallel, we only keep one of the peaks and reject the other. 1305 If only one of the peaks is a PSF template, the other template is used, 1306 otherwise the one with the maximum template value is kept. 1310 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1311 Container for the final deblender results. 1313 LSST logger for logging purposes. 1314 maxTempDotProd: `float`, optional 1315 All dot products between templates greater than ``maxTempDotProd`` will result in one 1316 of the templates removed. 1321 If any degenerate templates are found, ``modified`` is ``True``. 1323 log.trace(
'Looking for degnerate templates')
1326 for fidx
in debResult.filters:
1327 dp = debResult.deblendedParents[fidx]
1328 nchild = np.sum([pkres.skip
is False for pkres
in dp.peaks])
1329 indexes = [pkres.pki
for pkres
in dp.peaks
if pkres.skip
is False]
1334 A = np.zeros((nchild, nchild))
1337 for pkres
in dp.peaks:
1340 heavies.append(afwDet.makeHeavyFootprint(pkres.templateFootprint,
1341 afwImage.MaskedImageF(pkres.templateImage)))
1342 maxTemplate.append(np.max(pkres.templateImage.getArray()))
1344 for i
in range(nchild):
1345 for j
in range(i + 1):
1346 A[i, j] = heavies[i].dot(heavies[j])
1349 for i
in range(nchild):
1351 norm = A[i, i]*A[j, j]
1355 A[i, j] /= np.sqrt(norm)
1360 for i
in range(nchild):
1363 if A[i, j] > currentMax:
1364 currentMax = A[i, j]
1365 if currentMax > maxTempDotProd:
1378 reject = indexes[rejectedIndex]
1379 if dp.peaks[keep].deblendedAsPsf
and dp.peaks[reject].deblendedAsPsf
is False:
1380 keep = indexes[rejectedIndex]
1382 elif dp.peaks[keep].deblendedAsPsf
is False and dp.peaks[reject].deblendedAsPsf:
1383 reject = indexes[rejectedIndex]
1386 if maxTemplate[rejectedIndex] > maxTemplate[i]:
1387 keep = indexes[rejectedIndex]
1389 log.trace(
'Removing object with index %d : %f. Degenerate with %d' % (reject, currentMax,
1391 dp.peaks[reject].skip =
True 1392 dp.peaks[reject].degenerate =
True 1397 def apportionFlux(debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak',
1398 strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001,
1399 getTemplateSum=False):
1400 """Apportion flux to all of the peak templates in each filter 1402 Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of 1403 flux assigned to each ``template``. 1404 Leftover "stray flux" is assigned to peaks based on the other parameters. 1408 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1409 Container for the final deblender results. 1411 LSST logger for logging purposes. 1412 assignStrayFlux: `bool`, optional 1413 If True then flux in the parent footprint that is not covered by any of the 1414 template footprints is assigned to templates based on their 1/(1+r^2) distance. 1415 How the flux is apportioned is determined by ``strayFluxAssignment``. 1416 strayFluxAssignment: `string`, optional 1417 Determines how stray flux is apportioned. 1418 * ``trim``: Trim stray flux and do not include in any footprints 1419 * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks 1420 * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the 1421 minimum distance from the stray flux to footprint 1422 * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan) 1423 distance to the stray flux 1424 strayFluxToPointSources: `string`, optional 1425 Determines how stray flux is apportioned to point sources 1426 * ``never``: never apportion stray flux to point sources 1427 * ``necessary`` (default): point sources are included only if there are no extended sources nearby 1428 * ``always``: point sources are always included in the 1/(1+r^2) splitting 1429 clipStrayFluxFraction: `float`, optional 1430 Minimum stray-flux portion. 1431 Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero. 1432 getTemplateSum: `bool`, optional 1433 As part of the flux calculation, the sum of the templates is calculated. 1434 If ``getTemplateSum==True`` then the sum of the templates is stored in the result 1435 (a `DeblendedFootprint`). 1440 Apportion flux always modifies the templates, so ``modified`` is always ``True``. 1441 However, this should likely be the final step and it is unlikely that 1442 any deblender plugins will be re-run. 1444 validStrayPtSrc = [
'never',
'necessary',
'always']
1445 validStrayAssign = [
'r-to-peak',
'r-to-footprint',
'nearest-footprint',
'trim']
1446 if strayFluxToPointSources
not in validStrayPtSrc:
1447 raise ValueError(((
'strayFluxToPointSources: value \"%s\" not in the set of allowed values: ') %
1448 strayFluxToPointSources) + str(validStrayPtSrc))
1449 if strayFluxAssignment
not in validStrayAssign:
1450 raise ValueError(((
'strayFluxAssignment: value \"%s\" not in the set of allowed values: ') %
1451 strayFluxAssignment) + str(validStrayAssign))
1453 for fidx
in debResult.filters:
1454 dp = debResult.deblendedParents[fidx]
1467 bb = dp.fp.getBBox()
1469 for peaki, pkres
in enumerate(dp.peaks):
1472 tmimgs.append(pkres.templateImage)
1473 tfoots.append(pkres.templateFootprint)
1475 dpsf.append(pkres.deblendedAsPsf)
1477 pkx.append(pk.getIx())
1478 pky.append(pk.getIy())
1479 ibi.append(pkres.pki)
1482 log.trace(
'Apportioning flux among %i templates', len(tmimgs))
1483 sumimg = afwImage.ImageF(bb)
1488 if strayFluxAssignment ==
'trim':
1489 assignStrayFlux =
False 1490 strayopts |= bUtils.STRAYFLUX_TRIM
1492 strayopts |= bUtils.ASSIGN_STRAYFLUX
1493 if strayFluxToPointSources ==
'necessary':
1494 strayopts |= bUtils.STRAYFLUX_TO_POINT_SOURCES_WHEN_NECESSARY
1495 elif strayFluxToPointSources ==
'always':
1496 strayopts |= bUtils.STRAYFLUX_TO_POINT_SOURCES_ALWAYS
1498 if strayFluxAssignment ==
'r-to-peak':
1501 elif strayFluxAssignment ==
'r-to-footprint':
1502 strayopts |= bUtils.STRAYFLUX_R_TO_FOOTPRINT
1503 elif strayFluxAssignment ==
'nearest-footprint':
1504 strayopts |= bUtils.STRAYFLUX_NEAREST_FOOTPRINT
1506 portions, strayflux = bUtils.apportionFlux(dp.maskedImage, dp.fp, tmimgs, tfoots, sumimg, dpsf,
1507 pkx, pky, strayopts, clipStrayFluxFraction)
1510 if strayFluxAssignment ==
'trim':
1513 finalSpanSet = finalSpanSet.union(foot.spans)
1514 dp.fp.setSpans(finalSpanSet)
1518 debResult.setTemplateSums(sumimg, fidx)
1522 for j, (pk, pkres)
in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1525 pkres.setFluxPortion(portions[ii])
1530 stray = strayflux[ii]
1535 pkres.setStrayFlux(stray)
1538 for j, (pk, pkres)
in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1542 for foot, add
in [(pkres.templateFootprint,
True), (pkres.origFootprint,
True),
1543 (pkres.strayFlux,
False)]:
1546 pks = foot.getPeaks()
def medianSmoothTemplates(debResult, log, medianFilterHalfsize=2)
def clipFootprintToNonzeroImpl(foot, image)
def clipFootprintsToNonzero(debResult, log)
def fitPsfs(debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2)
def reconstructTemplates(debResult, log, maxTempDotProd=0.5)
def rampFluxAtEdge(debResult, log, patchEdges=False)
def buildMultibandTemplates(debResult, log, useWeights=False, usePsf=False, sources=None, constraints=None, config=None, maxIter=100, bgScale=0.5, relativeError=1e-2, badMask=None)
def apportionFlux(debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak', strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001, getTemplateSum=False)
def buildSymmetricTemplates(debResult, log, patchEdges=False, setOrigTemplate=True)
def run(self, debResult, log)
def weightTemplates(debResult, log)
def __init__(self, func, onReset=None, maxIterations=50, kwargs)
def makeTemplatesMonotonic(debResult, log)