32 from .baselineUtils
import BaselineUtilsF
as bUtils
37 Clips the given *Footprint* to the region in the *Image* 38 containing non-zero values. The clipping drops spans that are 39 totally zero, and moves endpoints to non-zero; it does not 40 split spans that have internal zeros. 44 xImMax = x0 + image.getDimensions().getX()
45 yImMax = y0 + image.getDimensions().getY()
47 arr = image.getArray()
48 for span
in foot.spans:
50 if y < y0
or y > yImMax:
54 xMin = spanX0
if spanX0 >= x0
else x0
55 xMax = spanX1
if spanX1 <= xImMax
else xImMax
56 xarray = np.arange(xMin, xMax+1)[arr[y-y0, xMin-x0:xMax-x0+1] != 0]
61 foot.removeOrphanPeaks()
65 """Class to define plugins for the deblender. 67 The new deblender executes a series of plugins specified by the user. 68 Each plugin defines the function to be executed, the keyword arguments required by the function, 69 and whether or not certain portions of the deblender might need to be rerun as a result of 72 def __init__(self, func, onReset=None, maxIterations=50, **kwargs):
73 """Initialize a deblender plugin 78 Function to run when the plugin is executed. The function should always take 79 `debResult`, a `DeblenderResult` that stores the deblender result, and 80 `log`, an `lsst.log`, as the first two arguments, as well as any additional 81 keyword arguments (that must be specified in ``kwargs``). 82 The function should also return ``modified``, a `bool` that tells the deblender whether 83 or not any templates have been modified by the function. 84 If ``modified==True``, the deblender will go back to step ``onReset``, 85 unless the has already been run ``maxIterations``. 87 Index of the deblender plugin to return to if ``func`` modifies any templates. 88 The default is ``None``, which does not re-run any plugins. 90 Maximum number of times the deblender will reset when the current plugin 100 def run(self, debResult, log):
101 """Execute the current plugin 103 Once the plugin has finished, check to see if part of the deblender must be executed again. 105 log.trace(
"Executing %s", self.
func.__name__)
106 reset = self.
func(debResult, log, **self.
kwargs)
114 return (
"<Deblender Plugin: func={0}, kwargs={1}".format(self.
func.__name__, self.
kwargs))
120 def _setPeakError(debResult, log, pk, cx, cy, filters, msg, flag):
121 """Update the peak in each band with an error 123 This function logs an error that occurs during deblending and sets the 128 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 129 Container for the final deblender results. 131 LSST logger for logging purposes. 133 Number of the peak that failed 135 x coordinate of the peak 137 y coordinate of the peak 139 List of filter names for the exposures 141 Message to display in log traceback 143 Name of the flag to set 149 log.trace(
"Peak {0} at ({1},{2}):{3}".format(pk, cx, cy, msg))
150 for fidx, f
in enumerate(filters):
151 pkResult = debResult.deblendedParents[f].peaks[pk]
152 getattr(pkResult, flag)()
156 sources=None, constraints=None, config=None, maxIter=100, bgScale=0.5,
157 relativeError=1e-2, badMask=None):
158 """Run the Multiband Deblender to build templates 162 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 163 Container for the final deblender results. 165 LSST logger for logging purposes. 166 useWeights: bool, default=False 167 Whether or not to use the variance map in each filter for the fit. 168 usePsf: bool, default=False 169 Whether or not to convolve the image with the PSF in each band. 170 This is not yet implemented in an optimized algorithm, so it is recommended 171 to leave this term off for now 172 sources: list of `scarlet.source.Source` objects, default=None 173 List of sources to use in the blend. By default the 174 `scarlet.source.ExtendedSource` class is used, which initializes each 175 source as symmetric and monotonic about a peak in the footprint peak catalog. 176 constraints: `scarlet.constraint.Constraint`, default=None 177 Constraint to be applied to each source. If sources require different constraints, 178 a list of `sources` must be created instead, which ignores the `constraints` parameter. 179 When `constraints` is `None` the default constraints are used. 180 config: `scarlet.config.Config`, default=None 181 Configuration for the blend. 182 If `config` is `None` then the default `Config` is used. 183 maxIter: int, default=100 184 Maximum iterations for a single blend. 186 Amount to scale the background RMS to set the floor for deblender model sizes 187 relativeError: float, default=1e-2 188 Relative error to reach for convergence 189 badMask: list of str, default=`None` 190 List of mask plane names to mark bad pixels. 191 If `badPixelKeys` is `None`, the default keywords used are 192 `["BAD", "CR", "NO_DATA", "SAT", "SUSPECT"]`. 197 If any templates have been created then ``modified`` is ``True``, 198 otherwise it is ``False`` (meaning all of the peaks were skipped). 201 bbox = debResult.footprint.getBBox()
202 peakSchema = debResult.footprint.peaks.getSchema()
203 xmin = bbox.getMinX()
204 ymin = bbox.getMinY()
205 peaks = [[pk.y-ymin, pk.x-xmin]
for pk
in debResult.peaks]
209 mMaskedImage = debResult.mMaskedImage[:, debResult.footprint.getBBox()]
210 data = mMaskedImage.image.array
214 weights = 1/mMaskedImage.variance.array
216 weights = np.ones_like(data)
221 badMask = [
"BAD",
"CR",
"NO_DATA",
"SAT",
"SUSPECT"]
222 fpMask = afwImage.Mask(bbox)
223 debResult.footprint.spans.setMask(fpMask, 1)
224 fpMask = ~fpMask.getArray().astype(bool)
225 badPixels = mMaskedImage.mask.getPlaneBitMask(badMask)
226 mask = (mMaskedImage.mask.array & badPixels) | fpMask[
None, :]
227 weights[mask > 0] = 0
232 for psf
in debResult.psfs:
233 psfs.append(psf.computeKernelImage().array)
238 bg_rms = np.array([debResult.deblendedParents[f].avgNoise
for f
in debResult.filters])*bgScale
241 if constraints
is None or isinstance(constraints[0], scarlet.constraints.Constraint):
242 constraints = [constraints] * len(peaks)
244 scarlet.source.ExtendedSource(center=peak,
247 constraints=constraints[pk],
253 for pk, peak
in enumerate(peaks)
260 blend = scarlet.blend.Blend(components=sources)
261 blend.set_data(img=data, weights=weights, bg_rms=bg_rms, config=config)
262 blend.fit(maxIter, e_rel=relativeError)
263 except scarlet.source.SourceInitError
as e:
265 debResult.failed =
True 267 except np.linalg.LinAlgError:
268 log.warn(
"Deblend failed catastrophically, most likely due to no signal in the footprint")
269 debResult.failed =
True 271 debResult.blend = blend
275 for pk, source
in enumerate(blend.sources):
276 src = source.components[0]
280 if debResult.peaks[pk].skip:
283 cx = src.center[1]+xmin
284 cy = src.center[0]+ymin
285 icx = int(np.round(cx))
286 icy = int(np.round(cy))
287 imbb = debResult.deblendedParents[debResult.filters[0]].img.getBBox()
290 if not imbb.contains(afwGeom.Point2I(cx, cy)):
291 _setPeakError(debResult, log, pk, cx, cy, debResult.filters,
292 "peak center is not inside image",
"setOutOfBounds")
295 if np.sum(src.morph) == 0:
296 _setPeakError(debResult, log, pk, cx, cy, debResult.filters,
297 "had no flux",
"setFailedSymmetricTemplate")
301 model = blend.get_model(k=pk).astype(np.float32)
304 mask = afwImage.Mask(np.array(np.sum(model, axis=0) > 0, dtype=np.int32), xy0=xy0)
305 ss = afwGeom.SpanSet.fromMask(mask)
308 log.warn(
"No flux in parent footprint")
309 debResult.failed =
True 313 for fidx, f
in enumerate(debResult.filters):
314 pkResult = debResult.deblendedParents[f].peaks[pk]
315 tfoot = afwDet.Footprint(ss, peakSchema=peakSchema)
318 peakFlux = np.sum(src.sed[fidx]*src.morph[_cy, _cx])
319 tfoot.addPeak(cx, cy, peakFlux)
320 timg = afwImage.ImageF(model[fidx], xy0=xy0)
321 timg = timg[tfoot.getBBox()]
322 pkResult.setOrigTemplate(timg, tfoot)
323 pkResult.setTemplate(timg, tfoot)
324 pkResult.setFluxPortion(afwImage.MaskedImageF(timg))
325 pkResult.multiColorPeak.x = cx
326 pkResult.multiColorPeak.y = cy
327 pkResult.peak.setFx(cx)
328 pkResult.peak.setFy(cy)
329 pkResult.peak.setIx(icx)
330 pkResult.peak.setIy(icy)
334 def fitPsfs(debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2):
335 """Fit a PSF + smooth background model (linear) to a small region around each peak 337 This function will iterate over all filters in deblender result but does not compare 338 results across filters. 339 DeblendedPeaks that pass the cuts have their templates modified to the PSF + background model 340 and their ``deblendedAsPsf`` property set to ``True``. 342 This will likely be replaced in the future with a function that compares the psf chi-squared cuts 343 so that peaks flagged as point sources will be considered point sources in all bands. 347 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 348 Container for the final deblender results. 350 LSST logger for logging purposes. 351 psfChisqCut*: `float`, optional 352 ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to 353 be considered a PSF match without recentering. 354 A fit is also made that includes terms to recenter the PSF. 355 ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the 356 fit that includes recentering terms. 357 If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and 358 the peak footprint is fit again, this time to the new PSF. 359 If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it 360 passes the re-centering algorithm. 361 If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted, 362 but parameters for all three psf fits are stored in the ``DebldendedPeak``. 363 The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``. 364 tinyFootprintSize: `float`, optional 365 The PSF model is shrunk to the size that contains the original footprint. 366 If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)`` 367 then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit. 373 If any templates have been assigned to PSF point sources then ``modified`` is ``True``, 374 otherwise it is ``False``. 376 from .baseline
import CachingPsf
379 for fidx
in debResult.filters:
380 dp = debResult.deblendedParents[fidx]
381 peaks = dp.fp.getPeaks()
385 fmask = afwImage.Mask(dp.bb)
386 fmask.setXY0(dp.bb.getMinX(), dp.bb.getMinY())
387 dp.fp.spans.setMask(fmask, 1)
392 peakF = [pk.getF()
for pk
in peaks]
394 for pki, (pk, pkres, pkF)
in enumerate(zip(peaks, dp.peaks, peakF)):
395 log.trace(
'Filter %s, Peak %i', fidx, pki)
396 ispsf = _fitPsf(dp.fp, fmask, pk, pkF, pkres, dp.bb, peaks, peakF, log, cpsf, dp.psffwhm,
397 dp.img, dp.varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b, tinyFootprintSize)
398 modified = modified
or ispsf
402 def _fitPsf(fp, fmask, pk, pkF, pkres, fbb, peaks, peaksF, log, psf, psffwhm,
403 img, varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b,
406 """Fit a PSF + smooth background model (linear) to a small region around a peak. 408 See fitPsfs for a more thorough description, including all parameters not described below. 412 fp: `afw.detection.Footprint` 413 Footprint containing the Peaks to model. 414 fmask: `afw.image.Mask` 415 The Mask plane for pixels in the Footprint 416 pk: `afw.detection.PeakRecord` 417 The peak within the Footprint that we are going to fit with PSF model 418 pkF: `afw.geom.Point2D` 419 Floating point coordinates of the peak. 420 pkres: `meas.deblender.DeblendedPeak` 421 Peak results object that will hold the results. 422 fbb: `afw.geom.Box2I` 423 Bounding box of ``fp`` 424 peaks: `afw.detection.PeakCatalog` 425 Catalog of peaks contained in the parent footprint. 426 peaksF: list of `afw.geom.Point2D` 427 List of floating point coordinates of all of the peaks. 428 psf: list of `afw.detection.Psf`s 429 Psf of the ``maskedImage`` for each band. 430 psffwhm: list pf `float`s 431 FWHM of the ``maskedImage``'s ``psf`` in each band. 432 img: `afw.image.ImageF` 433 The image that contains the footprint. 434 varimg: `afw.image.ImageF` 435 The variance of the image that contains the footprint. 440 Whether or not the peak matches a PSF model. 450 R0 = int(np.ceil(psffwhm*1.))
452 R1 = int(np.ceil(psffwhm*1.5))
453 cx, cy = pkF.getX(), pkF.getY()
454 psfimg = psf.computeImage(cx, cy)
456 R2 = R1 + min(psfimg.getWidth(), psfimg.getHeight())/2.
458 pbb = psfimg.getBBox()
460 px0, py0 = psfimg.getX0(), psfimg.getY0()
464 if not pbb.contains(afwGeom.Point2I(int(cx), int(cy))):
465 pkres.setOutOfBounds()
469 xlo = int(np.floor(cx - R1))
470 ylo = int(np.floor(cy - R1))
471 xhi = int(np.ceil(cx + R1))
472 yhi = int(np.ceil(cy + R1))
473 stampbb = afwGeom.Box2I(afwGeom.Point2I(xlo, ylo), afwGeom.Point2I(xhi, yhi))
475 xlo, xhi = stampbb.getMinX(), stampbb.getMaxX()
476 ylo, yhi = stampbb.getMinY(), stampbb.getMaxY()
477 if xlo > xhi
or ylo > yhi:
478 log.trace(
'Skipping this peak: out of bounds')
479 pkres.setOutOfBounds()
483 if min(stampbb.getWidth(), stampbb.getHeight()) <= max(tinyFootprintSize, 2):
486 log.trace(
'Skipping this peak: tiny footprint / close to edge')
487 pkres.setTinyFootprint()
492 for pk2, pkF2
in zip(peaks, peaksF):
495 if pkF.distanceSquared(pkF2) > R2**2:
497 opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
498 if not opsfimg.getBBox().overlaps(stampbb):
500 otherpeaks.append(opsfimg)
501 log.trace(
'%i other peaks within range', len(otherpeaks))
507 NT1 = 4 + len(otherpeaks)
511 NP = (1 + yhi - ylo)*(1 + xhi - xlo)
523 ix0, iy0 = img.getX0(), img.getY0()
524 fx0, fy0 = fbb.getMinX(), fbb.getMinY()
525 fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
526 islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
527 fmask_sub = fmask .getArray()[fslice]
528 var_sub = varimg.getArray()[islice]
529 img_sub = img.getArray()[islice]
532 psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
533 pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
534 px0, px1 = pbb.getMinX(), pbb.getMaxX()
535 py0, py1 = pbb.getMinY(), pbb.getMaxY()
538 valid = (fmask_sub > 0)
539 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
540 RR = ((xx - cx)**2)[np.newaxis, :] + ((yy - cy)**2)[:, np.newaxis]
541 valid *= (RR <= R1**2)
542 valid *= (var_sub > 0)
546 log.warn(
'Skipping peak at (%.1f, %.1f): no unmasked pixels nearby', cx, cy)
547 pkres.setNoValidPixels()
551 XX, YY = np.meshgrid(xx, yy)
552 ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
554 inpsfx = (xx >= px0)*(xx <= px1)
555 inpsfy = (yy >= py0)*(yy <= py1)
556 inpsf = np.outer(inpsfy, inpsfx)
557 indx = np.outer(inpsfy, (xx > px0)*(xx < px1))
558 indy = np.outer((yy > py0)*(yy < py1), inpsfx)
563 def _overlap(xlo, xhi, xmin, xmax):
564 assert((xlo <= xmax)
and (xhi >= xmin)
and 565 (xlo <= xhi)
and (xmin <= xmax))
566 xloclamp = max(xlo, xmin)
568 xhiclamp = min(xhi, xmax)
569 Xhi = Xlo + (xhiclamp - xloclamp)
570 assert(xloclamp >= 0)
572 return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
574 A = np.zeros((NP, NT2))
578 A[:, I_sky_ramp_x] = ipixes[:, 0] + (xlo-cx)
579 A[:, I_sky_ramp_y] = ipixes[:, 1] + (ylo-cy)
582 px0, px1 = pbb.getMinX(), pbb.getMaxX()
583 py0, py1 = pbb.getMinY(), pbb.getMaxY()
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 psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
588 psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
589 psfsub = psfarr[psf_y_slice, psf_x_slice]
590 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
591 A[inpsf[valid], I_psf] = psfsub[vsub]
595 oldsx = (sx1, sx2, sx3, sx4)
596 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0+1, px1-1)
597 psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1] -
598 psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1])/2.
599 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
600 A[indx[valid], I_dx] = psfsub[vsub]
602 (sx1, sx2, sx3, sx4) = oldsx
605 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0+1, py1-1)
606 psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice] -
607 psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice])/2.
608 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
609 A[indy[valid], I_dy] = psfsub[vsub]
612 for j, opsf
in enumerate(otherpeaks):
614 ino = np.outer((yy >= obb.getMinY())*(yy <= obb.getMaxY()),
615 (xx >= obb.getMinX())*(xx <= obb.getMaxX()))
616 dpx0, dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
617 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
618 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
619 opsfarr = opsf.getArray()
620 psfsub = opsfarr[sy3 - dpy0: sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
621 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
622 A[ino[valid], I_opsf + j] = psfsub[vsub]
628 rw = np.ones_like(RR)
631 rw[ii] = np.maximum(0, 1. - ((rr - R0)/(R1 - R0)))
632 w = np.sqrt(rw[valid]/var_sub[valid])
634 sumr = np.sum(rw[valid])
635 log.debug(
'sumr = %g', sumr)
639 Aw = A*w[:, np.newaxis]
648 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
649 im1[ipixes[:, 1], ipixes[:, 0]] = A[:, i]
650 plt.subplot(R, C, i+1)
651 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
652 plt.subplot(R, C, NT2+1)
653 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
654 im1[ipixes[:, 1], ipixes[:, 0]] = b
655 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
656 plt.subplot(R, C, NT2+2)
657 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
658 im1[ipixes[:, 1], ipixes[:, 0]] = w
659 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
671 X1, r1, rank1, s1 = np.linalg.lstsq(Aw[:, :NT1], bw, rcond=-1)
673 X2, r2, rank2, s2 = np.linalg.lstsq(Aw, bw, rcond=-1)
674 except np.linalg.LinAlgError
as e:
675 log.warn(
"Failed to fit PSF to child: %s", e)
676 pkres.setPsfFitFailed()
679 log.debug(
'r1 r2 %s %s', r1, r2)
691 dof1 = sumr - len(X1)
692 dof2 = sumr - len(X2)
693 log.debug(
'dof1, dof2 %g %g', dof1, dof2)
696 if dof1 <= 0
or dof2 <= 0:
697 log.trace(
'Skipping this peak: bad DOF %g, %g', dof1, dof2)
703 log.trace(
'PSF fits: chisq/dof = %g, %g', q1, q2)
704 ispsf1 = (q1 < psfChisqCut1)
705 ispsf2 = (q2 < psfChisqCut2)
707 pkres.psfFit1 = (chisq1, dof1)
708 pkres.psfFit2 = (chisq2, dof2)
712 fdx, fdy = X2[I_dx], X2[I_dy]
717 ispsf2 = ispsf2
and (abs(dx) < 1.
and abs(dy) < 1.)
718 log.trace(
'isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s', dx, dy, str(ispsf2))
720 pkres.psfFitBigDecenter =
True 725 psfimg2 = psf.computeImage(cx + dx, cy + dy)
727 pbb2 = psfimg2.getBBox()
732 if not pbb2.contains(afwGeom.Point2I(int(cx + dx), int(cy + dy))):
736 px0, py0 = psfimg2.getX0(), psfimg2.getY0()
737 psfarr = psfimg2.getArray()[pbb2.getMinY()-py0:1+pbb2.getMaxY()-py0,
738 pbb2.getMinX()-px0:1+pbb2.getMaxX()-px0]
739 px0, py0 = pbb2.getMinX(), pbb2.getMinY()
740 px1, py1 = pbb2.getMaxX(), pbb2.getMaxY()
745 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
746 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
747 dpx0, dpy0 = px0 - xlo, py0 - ylo
748 psfsub = psfarr[sy3-dpy0:sy4-dpy0, sx3-dpx0:sx4-dpx0]
749 vsub = valid[sy1-ylo:sy2-ylo, sx1-xlo:sx2-xlo]
750 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
751 inpsf = np.outer((yy >= py0)*(yy <= py1), (xx >= px0)*(xx <= px1))
752 Ab[inpsf[valid], I_psf] = psfsub[vsub]
754 Aw = Ab*w[:, np.newaxis]
756 Xb, rb, rankb, sb = np.linalg.lstsq(Aw, bw, rcond=-1)
761 dofb = sumr - len(Xb)
763 ispsf2 = (qb < psfChisqCut2b)
766 log.trace(
'shifted PSF: new chisq/dof = %g; good? %s', qb, ispsf2)
767 pkres.psfFit3 = (chisqb, dofb)
770 if (((ispsf1
and ispsf2)
and (q2 < q1))
or 771 (ispsf2
and not ispsf1)):
775 log.debug(
'dof %g', dof)
776 log.trace(
'Keeping shifted-PSF model')
779 pkres.psfFitWithDecenter =
True 785 log.debug(
'dof %g', dof)
786 log.trace(
'Keeping unshifted PSF model')
788 ispsf = (ispsf1
or ispsf2)
792 SW, SH = 1+xhi-xlo, 1+yhi-ylo
793 psfmod = afwImage.ImageF(SW, SH)
794 psfmod.setXY0(xlo, ylo)
795 psfderivmodm = afwImage.MaskedImageF(SW, SH)
796 psfderivmod = psfderivmodm.getImage()
797 psfderivmod.setXY0(xlo, ylo)
798 model = afwImage.ImageF(SW, SH)
799 model.setXY0(xlo, ylo)
800 for i
in range(len(Xpsf)):
801 for (x, y), v
in zip(ipixes, A[:, i]*Xpsf[i]):
802 ix, iy = int(x), int(y)
803 model.set(ix, iy, model.get(ix, iy) + float(v))
804 if i
in [I_psf, I_dx, I_dy]:
805 psfderivmod.set(ix, iy, psfderivmod.get(ix, iy) + float(v))
808 psfmod.set(int(x), int(y), float(A[ii, I_psf]*Xpsf[I_psf]))
809 modelfp = afwDet.Footprint(fp.getPeaks().getSchema())
810 for (x, y)
in ipixes:
811 modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
814 pkres.psfFitDebugPsf0Img = psfimg
815 pkres.psfFitDebugPsfImg = psfmod
816 pkres.psfFitDebugPsfDerivImg = psfderivmod
817 pkres.psfFitDebugPsfModel = model
818 pkres.psfFitDebugStamp = img.Factory(img, stampbb,
True)
819 pkres.psfFitDebugValidPix = valid
820 pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb,
True)
821 ww = np.zeros(valid.shape, np.float)
823 pkres.psfFitDebugWeight = ww
824 pkres.psfFitDebugRampWeight = rw
829 pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
830 pkres.psfFitCenter = (cx, cy)
831 log.debug(
'saving chisq,dof %g %g', chisq, dof)
832 pkres.psfFitBest = (chisq, dof)
833 pkres.psfFitParams = Xpsf
834 pkres.psfFitFlux = Xpsf[I_psf]
835 pkres.psfFitNOthers = len(otherpeaks)
838 pkres.setDeblendedAsPsf()
842 log.trace(
'Deblending as PSF; setting template to PSF model')
845 psfimg = psf.computeImage(cx, cy)
847 psfimg *= Xpsf[I_psf]
848 psfimg = psfimg.convertF()
851 fpcopy = afwDet.Footprint(fp)
852 psfbb = psfimg.getBBox()
854 bb = fpcopy.getBBox()
857 psfmod = afwImage.ImageF(bb)
858 fpcopy.spans.copyImage(psfimg, psfmod)
861 pkres.setTemplate(psfmod, fpcopy)
864 pkres.setPsfTemplate(psfmod, fpcopy)
870 """Build a symmetric template for each peak in each filter 872 Given ``maskedImageF``, ``footprint``, and a ``DebldendedPeak``, creates a symmetric template 873 (``templateImage`` and ``templateFootprint``) around the peak for all peaks not flagged as 874 ``skip`` or ``deblendedAsPsf``. 878 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 879 Container for the final deblender results. 881 LSST logger for logging purposes. 882 patchEdges: `bool`, optional 883 If True and if the parent Footprint touches pixels with the ``EDGE`` bit set, 884 then grow the parent Footprint to include all symmetric templates. 889 If any peaks are not skipped or marked as point sources, ``modified`` is ``True. 890 Otherwise ``modified`` is ``False``. 894 for fidx
in debResult.filters:
895 dp = debResult.deblendedParents[fidx]
896 imbb = dp.img.getBBox()
897 log.trace(
'Creating templates for footprint at x0,y0,W,H = %i, %i, %i, %i)', dp.x0, dp.y0, dp.W, dp.H)
899 for peaki, pkres
in enumerate(dp.peaks):
900 log.trace(
'Deblending peak %i of %i', peaki, len(dp.peaks))
903 if pkres.skip
or pkres.deblendedAsPsf:
907 cx, cy = pk.getIx(), pk.getIy()
908 if not imbb.contains(afwGeom.Point2I(cx, cy)):
909 log.trace(
'Peak center is not inside image; skipping %i', pkres.pki)
910 pkres.setOutOfBounds()
912 log.trace(
'computing template for peak %i at (%i, %i)', pkres.pki, cx, cy)
913 timg, tfoot, patched = bUtils.buildSymmetricTemplate(dp.maskedImage, dp.fp, pk, dp.avgNoise,
916 log.trace(
'Peak %i at (%i, %i): failed to build symmetric template', pkres.pki, cx, cy)
917 pkres.setFailedSymmetricTemplate()
925 pkres.setOrigTemplate(timg, tfoot)
926 pkres.setTemplate(timg, tfoot)
931 """Adjust flux on the edges of the template footprints. 933 Using the PSF, a peak ``Footprint`` with pixels on the edge of ``footprint`` 934 is grown by the ``psffwhm``*1.5 and filled in with ramped pixels. 935 The result is a new symmetric footprint template for the peaks near the edge. 939 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 940 Container for the final deblender results. 942 LSST logger for logging purposes. 943 patchEdges: `bool`, optional 944 If True and if the parent Footprint touches pixels with the ``EDGE`` bit set, 945 then grow the parent Footprint to include all symmetric templates. 950 If any peaks have their templates modified to include flux at the edges, 951 ``modified`` is ``True``. 955 for fidx
in debResult.filters:
956 dp = debResult.deblendedParents[fidx]
957 log.trace(
'Checking for significant flux at edge: sigma1=%g', dp.avgNoise)
959 for peaki, pkres
in enumerate(dp.peaks):
960 if pkres.skip
or pkres.deblendedAsPsf:
962 timg, tfoot = pkres.templateImage, pkres.templateFootprint
963 if bUtils.hasSignificantFluxAtEdge(timg, tfoot, 3*dp.avgNoise):
964 log.trace(
"Template %i has significant flux at edge: ramping", pkres.pki)
966 (timg2, tfoot2, patched) = _handle_flux_at_edge(log, dp.psffwhm, timg, tfoot, dp.fp,
967 dp.maskedImage, dp.x0, dp.x1,
968 dp.y0, dp.y1, dp.psf, pkres.peak,
969 dp.avgNoise, patchEdges)
972 "CoaddPsf" in str(exc)):
973 pkres.setOutOfBounds()
976 pkres.setRampedTemplate(timg2, tfoot2)
979 pkres.setTemplate(timg2, tfoot2)
984 def _handle_flux_at_edge(log, psffwhm, t1, tfoot, fp, maskedImage,
985 x0, x1, y0, y1, psf, pk, sigma1, patchEdges):
986 """Extend a template by the PSF to fill in the footprint. 988 Using the PSF, a footprint that touches the edge is passed to the function 989 and is grown by the psffwhm*1.5 and filled in with ramped pixels. 994 LSST logger for logging purposes. 997 t1: `afw.image.ImageF` 998 The image template that contains the footprint to extend. 999 tfoot: `afw.detection.Footprint` 1000 Symmetric Footprint to extend. 1001 fp: `afw.detection.Footprint` 1002 Parent Footprint that is being deblended. 1003 maskedImage: `afw.image.MaskedImageF` 1004 Full MaskedImage containing the parent footprint ``fp``. 1006 Minimum x,y for the bounding box of the footprint ``fp``. 1008 Maximum x,y for the bounding box of the footprint ``fp``. 1009 psf: `afw.detection.Psf` 1011 pk: `afw.detection.PeakRecord` 1012 The peak within the Footprint whose footprint is being extended. 1014 Estimated noise level in the image. 1016 If ``patchEdges==True`` and if the footprint touches pixels with the 1017 ``EDGE`` bit set, then for spans whose symmetric mirror are outside the 1018 image, the symmetric footprint is grown to include them and their 1019 pixel values are stored. 1023 t2: `afw.image.ImageF` 1024 Image of the extended footprint. 1025 tfoot2: `afw.detection.Footprint` 1028 If the footprint touches an edge pixel, ``patched`` will be set to ``True``. 1029 Otherwise ``patched`` is ``False``. 1031 log.trace(
'Found significant flux at template edge.')
1041 S = int((S + 0.5)/2)*2 + 1
1043 tbb = tfoot.getBBox()
1048 fpcopy = afwDet.Footprint(fp)
1050 fpcopy.setSpans(fpcopy.spans.clippedTo(tbb))
1051 fpcopy.removeOrphanPeaks()
1052 padim = maskedImage.Factory(tbb)
1053 fpcopy.spans.clippedTo(maskedImage.getBBox()).copyMaskedImage(maskedImage, padim)
1056 edgepix = bUtils.getSignificantEdgePixels(t1, tfoot, -1e6)
1059 xc = int((x0 + x1)/2)
1060 yc = int((y0 + y1)/2)
1061 psfim = psf.computeImage(afwGeom.Point2D(xc, yc))
1062 pbb = psfim.getBBox()
1064 lx, ly = pbb.getMinX(), pbb.getMinY()
1065 psfim.setXY0(lx - xc, ly - yc)
1066 pbb = psfim.getBBox()
1068 Sbox = afwGeom.Box2I(afwGeom.Point2I(-S, -S), afwGeom.Extent2I(2*S+1, 2*S+1))
1069 if not Sbox.contains(pbb):
1071 psfim = psfim.Factory(psfim, Sbox, afwImage.PARENT,
True)
1072 pbb = psfim.getBBox()
1079 ramped = t1.Factory(tbb)
1080 Tout = ramped.getArray()
1082 tx0, ty0 = t1.getX0(), t1.getY0()
1083 ox0, oy0 = ramped.getX0(), ramped.getY0()
1084 P = psfim.getArray()
1087 for span
in edgepix.getSpans():
1089 for x
in range(span.getX0(), span.getX1()+1):
1090 slc = (slice(y+py0 - oy0, y+py1+1 - oy0),
1091 slice(x+px0 - ox0, x+px1+1 - ox0))
1092 Tout[slc] = np.maximum(Tout[slc], Tin[y-ty0, x-tx0]*P)
1096 imZeros = (padim.getImage().getArray() == 0)
1097 padim.getImage().getArray()[imZeros] = ramped.getArray()[imZeros]
1099 t2, tfoot2, patched = bUtils.buildSymmetricTemplate(padim, fpcopy, pk, sigma1,
True, patchEdges)
1104 imbb = maskedImage.getBBox()
1106 tbb = tfoot2.getBBox()
1108 t2 = t2.Factory(t2, tbb, afwImage.PARENT,
True)
1110 return t2, tfoot2, patched
1114 """Applying median smoothing filter to the template images for every peak in every filter. 1118 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1119 Container for the final deblender results. 1121 LSST logger for logging purposes. 1122 medianFilterHalfSize: `int`, optional 1123 Half the box size of the median filter, i.e. a ``medianFilterHalfSize`` of 50 means that 1124 each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image. 1125 This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored. 1130 Whether or not any templates were modified. 1131 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1135 for fidx
in debResult.filters:
1136 dp = debResult.deblendedParents[fidx]
1137 for peaki, pkres
in enumerate(dp.peaks):
1138 if pkres.skip
or pkres.deblendedAsPsf:
1141 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1142 filtsize = medianFilterHalfsize*2 + 1
1143 if timg.getWidth() >= filtsize
and timg.getHeight() >= filtsize:
1144 log.trace(
'Median filtering template %i', pkres.pki)
1147 inimg = timg.Factory(timg,
True)
1148 bUtils.medianFilter(inimg, timg, medianFilterHalfsize)
1150 pkres.setMedianFilteredTemplate(timg, tfoot)
1152 log.trace(
'Not median-filtering template %i: size %i x %i smaller than required %i x %i',
1153 pkres.pki, timg.getWidth(), timg.getHeight(), filtsize, filtsize)
1154 pkres.setTemplate(timg, tfoot)
1159 """Make the templates monotonic. 1161 The pixels in the templates are modified such that pixels further from the peak will 1162 have values smaller than those closer to the peak. 1166 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1167 Container for the final deblender results. 1169 LSST logger for logging purposes. 1174 Whether or not any templates were modified. 1175 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1179 for fidx
in debResult.filters:
1180 dp = debResult.deblendedParents[fidx]
1181 for peaki, pkres
in enumerate(dp.peaks):
1182 if pkres.skip
or pkres.deblendedAsPsf:
1185 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1187 log.trace(
'Making template %i monotonic', pkres.pki)
1188 bUtils.makeMonotonic(timg, pk)
1189 pkres.setTemplate(timg, tfoot)
1194 """Clip non-zero spans in the template footprints for every peak in each filter. 1196 Peak ``Footprint``s are clipped to the region in the image containing non-zero values 1197 by dropping spans that are completely zero and moving endpoints to non-zero pixels 1198 (but does not split spans that have internal zeros). 1202 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1203 Container for the final deblender results. 1205 LSST logger for logging purposes. 1210 Whether or not any templates were modified. 1211 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1214 for fidx
in debResult.filters:
1215 dp = debResult.deblendedParents[fidx]
1216 for peaki, pkres
in enumerate(dp.peaks):
1217 if pkres.skip
or pkres.deblendedAsPsf:
1219 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1221 if not tfoot.getBBox().isEmpty()
and tfoot.getBBox() != timg.getBBox(afwImage.PARENT):
1222 timg = timg.Factory(timg, tfoot.getBBox(), afwImage.PARENT,
True)
1223 pkres.setTemplate(timg, tfoot)
1228 """Weight the templates to best fit the observed image in each filter 1230 This function re-weights the templates so that their linear combination best represents 1231 the observed image in that filter. 1232 In the future it may be useful to simultaneously weight all of the filters together. 1236 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1237 Container for the final deblender results. 1239 LSST logger for logging purposes. 1244 ``weightTemplates`` does not actually modify the ``Footprint`` templates other than 1245 to add a weight to them, so ``modified`` is always ``False``. 1248 log.trace(
'Weighting templates')
1249 for fidx
in debResult.filters:
1250 _weightTemplates(debResult.deblendedParents[fidx])
1254 def _weightTemplates(dp):
1255 """Weight the templates to best match the parent Footprint in a single filter 1257 This includes weighting both regular templates and point source templates 1261 dp: `DeblendedParent` 1262 The deblended parent to re-weight 1268 nchild = np.sum([pkres.skip
is False for pkres
in dp.peaks])
1269 A = np.zeros((dp.W*dp.H, nchild))
1270 parentImage = afwImage.ImageF(dp.bb)
1271 afwDet.copyWithinFootprintImage(dp.fp, dp.img, parentImage)
1272 b = parentImage.getArray().ravel()
1275 for pkres
in dp.peaks:
1278 childImage = afwImage.ImageF(dp.bb)
1279 afwDet.copyWithinFootprintImage(dp.fp, pkres.templateImage, childImage)
1280 A[:, index] = childImage.getArray().ravel()
1283 X1, r1, rank1, s1 = np.linalg.lstsq(A, b, rcond=-1)
1288 for pkres
in dp.peaks:
1291 pkres.templateImage *= X1[index]
1292 pkres.setTemplateWeight(X1[index])
1297 """Remove "degenerate templates" 1299 If galaxies have substructure, such as face-on spirals, the process of identifying peaks can 1300 "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite 1301 similar because they represent the same galaxy, so we try to identify these "degenerate" peaks 1302 by looking at the inner product (in pixel space) of pairs of templates. 1303 If they are nearly parallel, we only keep one of the peaks and reject the other. 1304 If only one of the peaks is a PSF template, the other template is used, 1305 otherwise the one with the maximum template value is kept. 1309 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1310 Container for the final deblender results. 1312 LSST logger for logging purposes. 1313 maxTempDotProd: `float`, optional 1314 All dot products between templates greater than ``maxTempDotProd`` will result in one 1315 of the templates removed. 1320 If any degenerate templates are found, ``modified`` is ``True``. 1322 log.trace(
'Looking for degnerate templates')
1325 for fidx
in debResult.filters:
1326 dp = debResult.deblendedParents[fidx]
1327 nchild = np.sum([pkres.skip
is False for pkres
in dp.peaks])
1328 indexes = [pkres.pki
for pkres
in dp.peaks
if pkres.skip
is False]
1333 A = np.zeros((nchild, nchild))
1336 for pkres
in dp.peaks:
1339 heavies.append(afwDet.makeHeavyFootprint(pkres.templateFootprint,
1340 afwImage.MaskedImageF(pkres.templateImage)))
1341 maxTemplate.append(np.max(pkres.templateImage.getArray()))
1343 for i
in range(nchild):
1344 for j
in range(i + 1):
1345 A[i, j] = heavies[i].dot(heavies[j])
1348 for i
in range(nchild):
1350 norm = A[i, i]*A[j, j]
1354 A[i, j] /= np.sqrt(norm)
1359 for i
in range(nchild):
1362 if A[i, j] > currentMax:
1363 currentMax = A[i, j]
1364 if currentMax > maxTempDotProd:
1377 reject = indexes[rejectedIndex]
1378 if dp.peaks[keep].deblendedAsPsf
and dp.peaks[reject].deblendedAsPsf
is False:
1379 keep = indexes[rejectedIndex]
1381 elif dp.peaks[keep].deblendedAsPsf
is False and dp.peaks[reject].deblendedAsPsf:
1382 reject = indexes[rejectedIndex]
1385 if maxTemplate[rejectedIndex] > maxTemplate[i]:
1386 keep = indexes[rejectedIndex]
1388 log.trace(
'Removing object with index %d : %f. Degenerate with %d' % (reject, currentMax,
1390 dp.peaks[reject].skip =
True 1391 dp.peaks[reject].degenerate =
True 1396 def apportionFlux(debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak',
1397 strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001,
1398 getTemplateSum=False):
1399 """Apportion flux to all of the peak templates in each filter 1401 Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of 1402 flux assigned to each ``template``. 1403 Leftover "stray flux" is assigned to peaks based on the other parameters. 1407 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1408 Container for the final deblender results. 1410 LSST logger for logging purposes. 1411 assignStrayFlux: `bool`, optional 1412 If True then flux in the parent footprint that is not covered by any of the 1413 template footprints is assigned to templates based on their 1/(1+r^2) distance. 1414 How the flux is apportioned is determined by ``strayFluxAssignment``. 1415 strayFluxAssignment: `string`, optional 1416 Determines how stray flux is apportioned. 1417 * ``trim``: Trim stray flux and do not include in any footprints 1418 * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks 1419 * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the 1420 minimum distance from the stray flux to footprint 1421 * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan) 1422 distance to the stray flux 1423 strayFluxToPointSources: `string`, optional 1424 Determines how stray flux is apportioned to point sources 1425 * ``never``: never apportion stray flux to point sources 1426 * ``necessary`` (default): point sources are included only if there are no extended sources nearby 1427 * ``always``: point sources are always included in the 1/(1+r^2) splitting 1428 clipStrayFluxFraction: `float`, optional 1429 Minimum stray-flux portion. 1430 Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero. 1431 getTemplateSum: `bool`, optional 1432 As part of the flux calculation, the sum of the templates is calculated. 1433 If ``getTemplateSum==True`` then the sum of the templates is stored in the result 1434 (a `DeblendedFootprint`). 1439 Apportion flux always modifies the templates, so ``modified`` is always ``True``. 1440 However, this should likely be the final step and it is unlikely that 1441 any deblender plugins will be re-run. 1443 validStrayPtSrc = [
'never',
'necessary',
'always']
1444 validStrayAssign = [
'r-to-peak',
'r-to-footprint',
'nearest-footprint',
'trim']
1445 if strayFluxToPointSources
not in validStrayPtSrc:
1446 raise ValueError(((
'strayFluxToPointSources: value \"%s\" not in the set of allowed values: ') %
1447 strayFluxToPointSources) + str(validStrayPtSrc))
1448 if strayFluxAssignment
not in validStrayAssign:
1449 raise ValueError(((
'strayFluxAssignment: value \"%s\" not in the set of allowed values: ') %
1450 strayFluxAssignment) + str(validStrayAssign))
1452 for fidx
in debResult.filters:
1453 dp = debResult.deblendedParents[fidx]
1466 bb = dp.fp.getBBox()
1468 for peaki, pkres
in enumerate(dp.peaks):
1471 tmimgs.append(pkres.templateImage)
1472 tfoots.append(pkres.templateFootprint)
1474 dpsf.append(pkres.deblendedAsPsf)
1476 pkx.append(pk.getIx())
1477 pky.append(pk.getIy())
1478 ibi.append(pkres.pki)
1481 log.trace(
'Apportioning flux among %i templates', len(tmimgs))
1482 sumimg = afwImage.ImageF(bb)
1487 if strayFluxAssignment ==
'trim':
1488 assignStrayFlux =
False 1489 strayopts |= bUtils.STRAYFLUX_TRIM
1491 strayopts |= bUtils.ASSIGN_STRAYFLUX
1492 if strayFluxToPointSources ==
'necessary':
1493 strayopts |= bUtils.STRAYFLUX_TO_POINT_SOURCES_WHEN_NECESSARY
1494 elif strayFluxToPointSources ==
'always':
1495 strayopts |= bUtils.STRAYFLUX_TO_POINT_SOURCES_ALWAYS
1497 if strayFluxAssignment ==
'r-to-peak':
1500 elif strayFluxAssignment ==
'r-to-footprint':
1501 strayopts |= bUtils.STRAYFLUX_R_TO_FOOTPRINT
1502 elif strayFluxAssignment ==
'nearest-footprint':
1503 strayopts |= bUtils.STRAYFLUX_NEAREST_FOOTPRINT
1505 portions, strayflux = bUtils.apportionFlux(dp.maskedImage, dp.fp, tmimgs, tfoots, sumimg, dpsf,
1506 pkx, pky, strayopts, clipStrayFluxFraction)
1509 if strayFluxAssignment ==
'trim':
1512 finalSpanSet = finalSpanSet.union(foot.spans)
1513 dp.fp.setSpans(finalSpanSet)
1517 debResult.setTemplateSums(sumimg, fidx)
1521 for j, (pk, pkres)
in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1524 pkres.setFluxPortion(portions[ii])
1529 stray = strayflux[ii]
1534 pkres.setStrayFlux(stray)
1537 for j, (pk, pkres)
in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1541 for foot, add
in [(pkres.templateFootprint,
True), (pkres.origFootprint,
True),
1542 (pkres.strayFlux,
False)]:
1545 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)