24 from builtins
import range
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))
119 def _setPeakError(debResult, log, pk, cx, cy, filters, msg, flag):
120 """Update the peak in each band with an error 122 This function logs an error that occurs during deblending and sets the 127 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 128 Container for the final deblender results. 130 LSST logger for logging purposes. 132 Number of the peak that failed 134 x coordinate of the peak 136 y coordinate of the peak 138 List of filter names for the exposures 140 Message to display in log traceback 142 Name of the flag to set 148 log.trace(
"Peak {0} at ({1},{2}):{3}".format(pk, cx, cy, msg))
149 for fidx, f
in enumerate(filters):
150 pkResult = debResult.deblendedParents[f].peaks[pk]
151 getattr(pkResult, flag)()
154 sources=None, constraints=None, config=None, maxIter=100, bgScale=0.5,
155 relativeError=1e-2, badMask=None):
156 """Run the Multiband Deblender to build templates 160 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 161 Container for the final deblender results. 163 LSST logger for logging purposes. 164 useWeights: bool, default=False 165 Whether or not to use the variance map in each filter for the fit. 166 usePsf: bool, default=False 167 Whether or not to convolve the image with the PSF in each band. 168 This is not yet implemented in an optimized algorithm, so it is recommended 169 to leave this term off for now 170 sources: list of `scarlet.source.Source` objects, default=None 171 List of sources to use in the blend. By default the 172 `scarlet.source.ExtendedSource` class is used, which initializes each 173 source as symmetric and monotonic about a peak in the footprint peak catalog. 174 constraints: `scarlet.constraint.Constraint`, default=None 175 Constraint to be applied to each source. If sources require different constraints, 176 a list of `sources` must be created instead, which ignores the `constraints` parameter. 177 When `constraints` is `None` the default constraints are used. 178 config: `scarlet.config.Config`, default=None 179 Configuration for the blend. 180 If `config` is `None` then the default `Config` is used. 181 maxIter: int, default=100 182 Maximum iterations for a single blend. 184 Amount to scale the background RMS to set the floor for deblender model sizes 185 relativeError: float, default=1e-2 186 Relative error to reach for convergence 187 badMask: list of str, default=`None` 188 List of mask plane names to mark bad pixels. 189 If `badPixelKeys` is `None`, the default keywords used are 190 `["BAD", "CR", "NO_DATA", "SAT", "SUSPECT"]`. 195 If any templates have been created then ``modified`` is ``True``, 196 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]
208 maskedImages = [mimg.Factory(mimg, debResult.footprint.getBBox(), PARENT)
209 for mimg
in debResult.maskedImages]
210 data = np.array([mimg.image.array
for mimg
in maskedImages])
214 weights = 1/np.array([mimg.variance.array
for mimg
in maskedImages])
216 weights = 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 mask = np.zeros(weights.shape, dtype=bool)
226 for fidx, mimg
in enumerate(maskedImages):
227 badPixels = mimg.mask.getPlaneBitMask(badMask)
228 mask[fidx] = (mimg.getMask().array & badPixels) | fpMask
234 for psf
in debResult.psfs:
235 psfs.append(psf.computeKernelImage().array)
240 bg_rms = np.array([debResult.deblendedParents[f].avgNoise
for f
in debResult.filters])*bgScale
243 if (constraints
is scarlet.constraints.Constraint
or 244 constraints
is scarlet.constraints.ConstraintList
246 constraints = [constraints.copy()
for peak
in peaks]
247 elif constraints
is None:
248 constraints = [
None]*len(peaks)
250 scarlet.source.ExtendedSource(center=peak,
253 constraints=constraints[pk],
259 for pk,peak
in enumerate(peaks)
266 blend = scarlet.blend.Blend(sources=sources, img=data, weights=weights, bg_rms=bg_rms, config=config)
267 blend.fit(steps=maxIter, e_rel=relativeError)
268 except np.linalg.LinAlgError
as e:
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, src
in enumerate(blend.sources):
280 if debResult.peaks[pk].skip:
283 cx = src.center[1]+xmin
284 cy = src.center[0]+ymin
285 imbb = debResult.deblendedParents[debResult.filters[0]].img.getBBox()
288 if not imbb.contains(afwGeom.Point2I(cx, cy)):
289 _setPeakError(debResult, log, pk, cx, cy, debResult.filters,
290 "peak center is not inside image",
"setOutOfBounds")
293 if np.sum(src.morph)==0:
294 _setPeakError(debResult, log, pk, cx, cy, debResult.filters,
295 "had no flux",
"setFailedSymmetricTemplate")
299 model = blend.get_model(m=pk, flat=
False)
300 model = model.astype(np.float32)
303 mask = afwImage.Mask(np.array(np.sum(model, axis=0)>0, dtype=np.int32),
304 xy0=debResult.footprint.getBBox().getBegin())
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=debResult.footprint.getBBox().getBegin())
321 timg = timg.Factory(timg, tfoot.getBBox(), PARENT)
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(int(np.round(cx)))
330 pkResult.peak.setIy(int(np.round(cy)))
333 def fitPsfs(debResult, log, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, tinyFootprintSize=2):
334 """Fit a PSF + smooth background model (linear) to a small region around each peak 336 This function will iterate over all filters in deblender result but does not compare 337 results across filters. 338 DeblendedPeaks that pass the cuts have their templates modified to the PSF + background model 339 and their ``deblendedAsPsf`` property set to ``True``. 341 This will likely be replaced in the future with a function that compares the psf chi-squared cuts 342 so that peaks flagged as point sources will be considered point sources in all bands. 346 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 347 Container for the final deblender results. 349 LSST logger for logging purposes. 350 psfChisqCut*: `float`, optional 351 ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to 352 be considered a PSF match without recentering. 353 A fit is also made that includes terms to recenter the PSF. 354 ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the 355 fit that includes recentering terms. 356 If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and 357 the peak footprint is fit again, this time to the new PSF. 358 If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it 359 passes the re-centering algorithm. 360 If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted, 361 but parameters for all three psf fits are stored in the ``DebldendedPeak``. 362 The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``. 363 tinyFootprintSize: `float`, optional 364 The PSF model is shrunk to the size that contains the original footprint. 365 If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)`` 366 then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit. 372 If any templates have been assigned to PSF point sources then ``modified`` is ``True``, 373 otherwise it is ``False``. 375 from .baseline
import CachingPsf
378 for fidx
in debResult.filters:
379 dp = debResult.deblendedParents[fidx]
380 peaks = dp.fp.getPeaks()
384 fmask = afwImage.Mask(dp.bb)
385 fmask.setXY0(dp.bb.getMinX(), dp.bb.getMinY())
386 dp.fp.spans.setMask(fmask, 1)
391 peakF = [pk.getF()
for pk
in peaks]
393 for pki, (pk, pkres, pkF)
in enumerate(zip(peaks, dp.peaks, peakF)):
394 log.trace(
'Filter %s, Peak %i', fidx, pki)
395 ispsf = _fitPsf(dp.fp, fmask, pk, pkF, pkres, dp.bb, peaks, peakF, log, cpsf, dp.psffwhm,
396 dp.img, dp.varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b, tinyFootprintSize)
397 modified = modified
or ispsf
400 def _fitPsf(fp, fmask, pk, pkF, pkres, fbb, peaks, peaksF, log, psf, psffwhm,
401 img, varimg, psfChisqCut1, psfChisqCut2, psfChisqCut2b,
404 """Fit a PSF + smooth background model (linear) to a small region around a peak. 406 See fitPsfs for a more thorough description, including all parameters not described below. 410 fp: `afw.detection.Footprint` 411 Footprint containing the Peaks to model. 412 fmask: `afw.image.Mask` 413 The Mask plane for pixels in the Footprint 414 pk: `afw.detection.PeakRecord` 415 The peak within the Footprint that we are going to fit with PSF model 416 pkF: `afw.geom.Point2D` 417 Floating point coordinates of the peak. 418 pkres: `meas.deblender.DeblendedPeak` 419 Peak results object that will hold the results. 420 fbb: `afw.geom.Box2I` 421 Bounding box of ``fp`` 422 peaks: `afw.detection.PeakCatalog` 423 Catalog of peaks contained in the parent footprint. 424 peaksF: list of `afw.geom.Point2D` 425 List of floating point coordinates of all of the peaks. 426 psf: list of `afw.detection.Psf`s 427 Psf of the ``maskedImage`` for each band. 428 psffwhm: list pf `float`s 429 FWHM of the ``maskedImage``'s ``psf`` in each band. 430 img: `afw.image.ImageF` 431 The image that contains the footprint. 432 varimg: `afw.image.ImageF` 433 The variance of the image that contains the footprint. 438 Whether or not the peak matches a PSF model. 448 R0 = int(np.ceil(psffwhm*1.))
450 R1 = int(np.ceil(psffwhm*1.5))
451 cx, cy = pkF.getX(), pkF.getY()
452 psfimg = psf.computeImage(cx, cy)
454 R2 = R1 + min(psfimg.getWidth(), psfimg.getHeight())/2.
456 pbb = psfimg.getBBox()
458 px0, py0 = psfimg.getX0(), psfimg.getY0()
462 if not pbb.contains(afwGeom.Point2I(int(cx), int(cy))):
463 pkres.setOutOfBounds()
467 xlo = int(np.floor(cx - R1))
468 ylo = int(np.floor(cy - R1))
469 xhi = int(np.ceil(cx + R1))
470 yhi = int(np.ceil(cy + R1))
471 stampbb = afwGeom.Box2I(afwGeom.Point2I(xlo, ylo), afwGeom.Point2I(xhi, yhi))
473 xlo, xhi = stampbb.getMinX(), stampbb.getMaxX()
474 ylo, yhi = stampbb.getMinY(), stampbb.getMaxY()
475 if xlo > xhi
or ylo > yhi:
476 log.trace(
'Skipping this peak: out of bounds')
477 pkres.setOutOfBounds()
481 if min(stampbb.getWidth(), stampbb.getHeight()) <= max(tinyFootprintSize, 2):
484 log.trace(
'Skipping this peak: tiny footprint / close to edge')
485 pkres.setTinyFootprint()
490 for pk2, pkF2
in zip(peaks, peaksF):
493 if pkF.distanceSquared(pkF2) > R2**2:
495 opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
496 if not opsfimg.getBBox().overlaps(stampbb):
498 otherpeaks.append(opsfimg)
499 log.trace(
'%i other peaks within range', len(otherpeaks))
505 NT1 = 4 + len(otherpeaks)
509 NP = (1 + yhi - ylo)*(1 + xhi - xlo)
521 ix0, iy0 = img.getX0(), img.getY0()
522 fx0, fy0 = fbb.getMinX(), fbb.getMinY()
523 fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
524 islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
525 fmask_sub = fmask .getArray()[fslice]
526 var_sub = varimg.getArray()[islice]
527 img_sub = img.getArray()[islice]
530 psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
531 pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
532 px0, px1 = pbb.getMinX(), pbb.getMaxX()
533 py0, py1 = pbb.getMinY(), pbb.getMaxY()
536 valid = (fmask_sub > 0)
537 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
538 RR = ((xx - cx)**2)[np.newaxis, :] + ((yy - cy)**2)[:, np.newaxis]
539 valid *= (RR <= R1**2)
540 valid *= (var_sub > 0)
544 log.warn(
'Skipping peak at (%.1f, %.1f): no unmasked pixels nearby', cx, cy)
545 pkres.setNoValidPixels()
549 XX, YY = np.meshgrid(xx, yy)
550 ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
552 inpsfx = (xx >= px0)*(xx <= px1)
553 inpsfy = (yy >= py0)*(yy <= py1)
554 inpsf = np.outer(inpsfy, inpsfx)
555 indx = np.outer(inpsfy, (xx > px0)*(xx < px1))
556 indy = np.outer((yy > py0)*(yy < py1), inpsfx)
561 def _overlap(xlo, xhi, xmin, xmax):
562 assert((xlo <= xmax)
and (xhi >= xmin)
and 563 (xlo <= xhi)
and (xmin <= xmax))
564 xloclamp = max(xlo, xmin)
566 xhiclamp = min(xhi, xmax)
567 Xhi = Xlo + (xhiclamp - xloclamp)
568 assert(xloclamp >= 0)
570 return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
572 A = np.zeros((NP, NT2))
576 A[:, I_sky_ramp_x] = ipixes[:, 0] + (xlo-cx)
577 A[:, I_sky_ramp_y] = ipixes[:, 1] + (ylo-cy)
580 px0, px1 = pbb.getMinX(), pbb.getMaxX()
581 py0, py1 = pbb.getMinY(), pbb.getMaxY()
582 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
583 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
584 dpx0, dpy0 = px0 - xlo, py0 - ylo
585 psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
586 psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
587 psfsub = psfarr[psf_y_slice, psf_x_slice]
588 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
589 A[inpsf[valid], I_psf] = psfsub[vsub]
593 oldsx = (sx1, sx2, sx3, sx4)
594 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0+1, px1-1)
595 psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1] -
596 psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1])/2.
597 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
598 A[indx[valid], I_dx] = psfsub[vsub]
600 (sx1, sx2, sx3, sx4) = oldsx
603 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0+1, py1-1)
604 psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice] -
605 psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice])/2.
606 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
607 A[indy[valid], I_dy] = psfsub[vsub]
610 for j, opsf
in enumerate(otherpeaks):
612 ino = np.outer((yy >= obb.getMinY())*(yy <= obb.getMaxY()),
613 (xx >= obb.getMinX())*(xx <= obb.getMaxX()))
614 dpx0, dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
615 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
616 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
617 opsfarr = opsf.getArray()
618 psfsub = opsfarr[sy3 - dpy0: sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
619 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
620 A[ino[valid], I_opsf + j] = psfsub[vsub]
626 rw = np.ones_like(RR)
629 rw[ii] = np.maximum(0, 1. - ((rr - R0)/(R1 - R0)))
630 w = np.sqrt(rw[valid]/var_sub[valid])
632 sumr = np.sum(rw[valid])
633 log.debug(
'sumr = %g', sumr)
637 Aw = A*w[:, np.newaxis]
646 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
647 im1[ipixes[:, 1], ipixes[:, 0]] = A[:, i]
648 plt.subplot(R, C, i+1)
649 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
650 plt.subplot(R, C, NT2+1)
651 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
652 im1[ipixes[:, 1], ipixes[:, 0]] = b
653 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
654 plt.subplot(R, C, NT2+2)
655 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
656 im1[ipixes[:, 1], ipixes[:, 0]] = w
657 plt.imshow(im1, interpolation=
'nearest', origin=
'lower')
669 X1, r1, rank1, s1 = np.linalg.lstsq(Aw[:, :NT1], bw, rcond=-1)
671 X2, r2, rank2, s2 = np.linalg.lstsq(Aw, bw, rcond=-1)
672 except np.linalg.LinAlgError
as e:
673 log.warn(
"Failed to fit PSF to child: %s", e)
674 pkres.setPsfFitFailed()
677 log.debug(
'r1 r2 %s %s', r1, r2)
689 dof1 = sumr - len(X1)
690 dof2 = sumr - len(X2)
691 log.debug(
'dof1, dof2 %g %g', dof1, dof2)
694 if dof1 <= 0
or dof2 <= 0:
695 log.trace(
'Skipping this peak: bad DOF %g, %g', dof1, dof2)
701 log.trace(
'PSF fits: chisq/dof = %g, %g', q1, q2)
702 ispsf1 = (q1 < psfChisqCut1)
703 ispsf2 = (q2 < psfChisqCut2)
705 pkres.psfFit1 = (chisq1, dof1)
706 pkres.psfFit2 = (chisq2, dof2)
710 fdx, fdy = X2[I_dx], X2[I_dy]
715 ispsf2 = ispsf2
and (abs(dx) < 1.
and abs(dy) < 1.)
716 log.trace(
'isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s', dx, dy, str(ispsf2))
718 pkres.psfFitBigDecenter =
True 723 psfimg2 = psf.computeImage(cx + dx, cy + dy)
725 pbb2 = psfimg2.getBBox()
730 if not pbb2.contains(afwGeom.Point2I(int(cx + dx), int(cy + dy))):
734 px0, py0 = psfimg2.getX0(), psfimg2.getY0()
735 psfarr = psfimg2.getArray()[pbb2.getMinY()-py0:1+pbb2.getMaxY()-py0,
736 pbb2.getMinX()-px0:1+pbb2.getMaxX()-px0]
737 px0, py0 = pbb2.getMinX(), pbb2.getMinY()
738 px1, py1 = pbb2.getMaxX(), pbb2.getMaxY()
743 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
744 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
745 dpx0, dpy0 = px0 - xlo, py0 - ylo
746 psfsub = psfarr[sy3-dpy0:sy4-dpy0, sx3-dpx0:sx4-dpx0]
747 vsub = valid[sy1-ylo:sy2-ylo, sx1-xlo:sx2-xlo]
748 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
749 inpsf = np.outer((yy >= py0)*(yy <= py1), (xx >= px0)*(xx <= px1))
750 Ab[inpsf[valid], I_psf] = psfsub[vsub]
752 Aw = Ab*w[:, np.newaxis]
754 Xb, rb, rankb, sb = np.linalg.lstsq(Aw, bw, rcond=-1)
759 dofb = sumr - len(Xb)
761 ispsf2 = (qb < psfChisqCut2b)
764 log.trace(
'shifted PSF: new chisq/dof = %g; good? %s', qb, ispsf2)
765 pkres.psfFit3 = (chisqb, dofb)
768 if (((ispsf1
and ispsf2)
and (q2 < q1))
or 769 (ispsf2
and not ispsf1)):
773 log.debug(
'dof %g', dof)
774 log.trace(
'Keeping shifted-PSF model')
777 pkres.psfFitWithDecenter =
True 783 log.debug(
'dof %g', dof)
784 log.trace(
'Keeping unshifted PSF model')
786 ispsf = (ispsf1
or ispsf2)
790 SW, SH = 1+xhi-xlo, 1+yhi-ylo
791 psfmod = afwImage.ImageF(SW, SH)
792 psfmod.setXY0(xlo, ylo)
793 psfderivmodm = afwImage.MaskedImageF(SW, SH)
794 psfderivmod = psfderivmodm.getImage()
795 psfderivmod.setXY0(xlo, ylo)
796 model = afwImage.ImageF(SW, SH)
797 model.setXY0(xlo, ylo)
798 for i
in range(len(Xpsf)):
799 for (x, y), v
in zip(ipixes, A[:, i]*Xpsf[i]):
800 ix, iy = int(x), int(y)
801 model.set(ix, iy, model.get(ix, iy) + float(v))
802 if i
in [I_psf, I_dx, I_dy]:
803 psfderivmod.set(ix, iy, psfderivmod.get(ix, iy) + float(v))
806 psfmod.set(int(x), int(y), float(A[ii, I_psf]*Xpsf[I_psf]))
807 modelfp = afwDet.Footprint(fp.getPeaks().getSchema())
808 for (x, y)
in ipixes:
809 modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
812 pkres.psfFitDebugPsf0Img = psfimg
813 pkres.psfFitDebugPsfImg = psfmod
814 pkres.psfFitDebugPsfDerivImg = psfderivmod
815 pkres.psfFitDebugPsfModel = model
816 pkres.psfFitDebugStamp = img.Factory(img, stampbb,
True)
817 pkres.psfFitDebugValidPix = valid
818 pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb,
True)
819 ww = np.zeros(valid.shape, np.float)
821 pkres.psfFitDebugWeight = ww
822 pkres.psfFitDebugRampWeight = rw
827 pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
828 pkres.psfFitCenter = (cx, cy)
829 log.debug(
'saving chisq,dof %g %g', chisq, dof)
830 pkres.psfFitBest = (chisq, dof)
831 pkres.psfFitParams = Xpsf
832 pkres.psfFitFlux = Xpsf[I_psf]
833 pkres.psfFitNOthers = len(otherpeaks)
836 pkres.setDeblendedAsPsf()
840 log.trace(
'Deblending as PSF; setting template to PSF model')
843 psfimg = psf.computeImage(cx, cy)
845 psfimg *= Xpsf[I_psf]
846 psfimg = psfimg.convertF()
849 fpcopy = afwDet.Footprint(fp)
850 psfbb = psfimg.getBBox()
852 bb = fpcopy.getBBox()
855 psfmod = afwImage.ImageF(bb)
856 fpcopy.spans.copyImage(psfimg, psfmod)
859 pkres.setTemplate(psfmod, fpcopy)
862 pkres.setPsfTemplate(psfmod, fpcopy)
867 """Build a symmetric template for each peak in each filter 869 Given ``maskedImageF``, ``footprint``, and a ``DebldendedPeak``, creates a symmetric template 870 (``templateImage`` and ``templateFootprint``) around the peak for all peaks not flagged as 871 ``skip`` or ``deblendedAsPsf``. 875 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 876 Container for the final deblender results. 878 LSST logger for logging purposes. 879 patchEdges: `bool`, optional 880 If True and if the parent Footprint touches pixels with the ``EDGE`` bit set, 881 then grow the parent Footprint to include all symmetric templates. 886 If any peaks are not skipped or marked as point sources, ``modified`` is ``True. 887 Otherwise ``modified`` is ``False``. 891 for fidx
in debResult.filters:
892 dp = debResult.deblendedParents[fidx]
893 imbb = dp.img.getBBox()
894 log.trace(
'Creating templates for footprint at x0,y0,W,H = %i, %i, %i, %i)', dp.x0, dp.y0, dp.W, dp.H)
896 for peaki, pkres
in enumerate(dp.peaks):
897 log.trace(
'Deblending peak %i of %i', peaki, len(dp.peaks))
900 if pkres.skip
or pkres.deblendedAsPsf:
904 cx, cy = pk.getIx(), pk.getIy()
905 if not imbb.contains(afwGeom.Point2I(cx, cy)):
906 log.trace(
'Peak center is not inside image; skipping %i', pkres.pki)
907 pkres.setOutOfBounds()
909 log.trace(
'computing template for peak %i at (%i, %i)', pkres.pki, cx, cy)
910 timg, tfoot, patched = butils.buildSymmetricTemplate(dp.maskedImage, dp.fp, pk, dp.avgNoise,
913 log.trace(
'Peak %i at (%i, %i): failed to build symmetric template', pkres.pki, cx, cy)
914 pkres.setFailedSymmetricTemplate()
922 pkres.setOrigTemplate(timg, tfoot)
923 pkres.setTemplate(timg, tfoot)
927 """Adjust flux on the edges of the template footprints. 929 Using the PSF, a peak ``Footprint`` with pixels on the edge of ``footprint`` 930 is grown by the ``psffwhm``*1.5 and filled in with ramped pixels. 931 The result is a new symmetric footprint template for the peaks near the edge. 935 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 936 Container for the final deblender results. 938 LSST logger for logging purposes. 939 patchEdges: `bool`, optional 940 If True and if the parent Footprint touches pixels with the ``EDGE`` bit set, 941 then grow the parent Footprint to include all symmetric templates. 946 If any peaks have their templates modified to include flux at the edges, 947 ``modified`` is ``True``. 951 for fidx
in debResult.filters:
952 dp = debResult.deblendedParents[fidx]
953 log.trace(
'Checking for significant flux at edge: sigma1=%g', dp.avgNoise)
955 for peaki, pkres
in enumerate(dp.peaks):
956 if pkres.skip
or pkres.deblendedAsPsf:
958 timg, tfoot = pkres.templateImage, pkres.templateFootprint
959 if butils.hasSignificantFluxAtEdge(timg, tfoot, 3*dp.avgNoise):
960 log.trace(
"Template %i has significant flux at edge: ramping", pkres.pki)
962 (timg2, tfoot2, patched) = _handle_flux_at_edge(log, dp.psffwhm, timg, tfoot, dp.fp,
963 dp.maskedImage, dp.x0, dp.x1,
964 dp.y0, dp.y1, dp.psf, pkres.peak,
965 dp.avgNoise, patchEdges)
968 and "CoaddPsf" in str(exc)):
969 pkres.setOutOfBounds()
972 pkres.setRampedTemplate(timg2, tfoot2)
975 pkres.setTemplate(timg2, tfoot2)
979 def _handle_flux_at_edge(log, psffwhm, t1, tfoot, fp, maskedImage,
980 x0, x1, y0, y1, psf, pk, sigma1, patchEdges
982 """Extend a template by the PSF to fill in the footprint. 984 Using the PSF, a footprint that touches the edge is passed to the function 985 and is grown by the psffwhm*1.5 and filled in with ramped pixels. 990 LSST logger for logging purposes. 993 t1: `afw.image.ImageF` 994 The image template that contains the footprint to extend. 995 tfoot: `afw.detection.Footprint` 996 Symmetric Footprint to extend. 997 fp: `afw.detection.Footprint` 998 Parent Footprint that is being deblended. 999 maskedImage: `afw.image.MaskedImageF` 1000 Full MaskedImage containing the parent footprint ``fp``. 1002 Minimum x,y for the bounding box of the footprint ``fp``. 1004 Maximum x,y for the bounding box of the footprint ``fp``. 1005 psf: `afw.detection.Psf` 1007 pk: `afw.detection.PeakRecord` 1008 The peak within the Footprint whose footprint is being extended. 1010 Estimated noise level in the image. 1012 If ``patchEdges==True`` and if the footprint touches pixels with the 1013 ``EDGE`` bit set, then for spans whose symmetric mirror are outside the 1014 image, the symmetric footprint is grown to include them and their 1015 pixel values are stored. 1019 t2: `afw.image.ImageF` 1020 Image of the extended footprint. 1021 tfoot2: `afw.detection.Footprint` 1024 If the footprint touches an edge pixel, ``patched`` will be set to ``True``. 1025 Otherwise ``patched`` is ``False``. 1027 log.trace(
'Found significant flux at template edge.')
1037 S = int((S + 0.5)/2)*2 + 1
1039 tbb = tfoot.getBBox()
1044 fpcopy = afwDet.Footprint(fp)
1046 fpcopy.setSpans(fpcopy.spans.clippedTo(tbb))
1047 fpcopy.removeOrphanPeaks()
1048 padim = maskedImage.Factory(tbb)
1049 fpcopy.spans.clippedTo(maskedImage.getBBox()).copyMaskedImage(maskedImage, padim)
1052 edgepix = butils.getSignificantEdgePixels(t1, tfoot, -1e6)
1055 xc = int((x0 + x1)/2)
1056 yc = int((y0 + y1)/2)
1057 psfim = psf.computeImage(afwGeom.Point2D(xc, yc))
1058 pbb = psfim.getBBox()
1060 lx, ly = pbb.getMinX(), pbb.getMinY()
1061 psfim.setXY0(lx - xc, ly - yc)
1062 pbb = psfim.getBBox()
1064 Sbox = afwGeom.Box2I(afwGeom.Point2I(-S, -S), afwGeom.Extent2I(2*S+1, 2*S+1))
1065 if not Sbox.contains(pbb):
1067 psfim = psfim.Factory(psfim, Sbox, afwImage.PARENT,
True)
1068 pbb = psfim.getBBox()
1075 ramped = t1.Factory(tbb)
1076 Tout = ramped.getArray()
1078 tx0, ty0 = t1.getX0(), t1.getY0()
1079 ox0, oy0 = ramped.getX0(), ramped.getY0()
1080 P = psfim.getArray()
1083 for span
in edgepix.getSpans():
1085 for x
in range(span.getX0(), span.getX1()+1):
1086 slc = (slice(y+py0 - oy0, y+py1+1 - oy0),
1087 slice(x+px0 - ox0, x+px1+1 - ox0))
1088 Tout[slc] = np.maximum(Tout[slc], Tin[y-ty0, x-tx0]*P)
1092 I = (padim.getImage().getArray() == 0)
1093 padim.getImage().getArray()[I] = ramped.getArray()[I]
1095 t2, tfoot2, patched = butils.buildSymmetricTemplate(padim, fpcopy, pk, sigma1,
True, patchEdges)
1100 imbb = maskedImage.getBBox()
1102 tbb = tfoot2.getBBox()
1104 t2 = t2.Factory(t2, tbb, afwImage.PARENT,
True)
1106 return t2, tfoot2, patched
1109 """Applying median smoothing filter to the template images for every peak in every filter. 1113 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1114 Container for the final deblender results. 1116 LSST logger for logging purposes. 1117 medianFilterHalfSize: `int`, optional 1118 Half the box size of the median filter, i.e. a ``medianFilterHalfSize`` of 50 means that 1119 each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image. 1120 This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored. 1125 Whether or not any templates were modified. 1126 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1130 for fidx
in debResult.filters:
1131 dp = debResult.deblendedParents[fidx]
1132 for peaki, pkres
in enumerate(dp.peaks):
1133 if pkres.skip
or pkres.deblendedAsPsf:
1136 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1137 filtsize = medianFilterHalfsize*2 + 1
1138 if timg.getWidth() >= filtsize
and timg.getHeight() >= filtsize:
1139 log.trace(
'Median filtering template %i', pkres.pki)
1142 inimg = timg.Factory(timg,
True)
1143 butils.medianFilter(inimg, timg, medianFilterHalfsize)
1145 pkres.setMedianFilteredTemplate(timg, tfoot)
1147 log.trace(
'Not median-filtering template %i: size %i x %i smaller than required %i x %i',
1148 pkres.pki, timg.getWidth(), timg.getHeight(), filtsize, filtsize)
1149 pkres.setTemplate(timg, tfoot)
1153 """Make the templates monotonic. 1155 The pixels in the templates are modified such that pixels further from the peak will 1156 have values smaller than those closer to the peak. 1160 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1161 Container for the final deblender results. 1163 LSST logger for logging purposes. 1168 Whether or not any templates were modified. 1169 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1173 for fidx
in debResult.filters:
1174 dp = debResult.deblendedParents[fidx]
1175 for peaki, pkres
in enumerate(dp.peaks):
1176 if pkres.skip
or pkres.deblendedAsPsf:
1179 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1181 log.trace(
'Making template %i monotonic', pkres.pki)
1182 butils.makeMonotonic(timg, pk)
1183 pkres.setTemplate(timg, tfoot)
1187 """Clip non-zero spans in the template footprints for every peak in each filter. 1189 Peak ``Footprint``s are clipped to the region in the image containing non-zero values 1190 by dropping spans that are completely zero and moving endpoints to non-zero pixels 1191 (but does not split spans that have internal zeros). 1195 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1196 Container for the final deblender results. 1198 LSST logger for logging purposes. 1203 Whether or not any templates were modified. 1204 This will be ``True`` as long as there is at least one source that is not flagged as a PSF. 1208 for fidx
in debResult.filters:
1209 dp = debResult.deblendedParents[fidx]
1210 for peaki, pkres
in enumerate(dp.peaks):
1211 if pkres.skip
or pkres.deblendedAsPsf:
1214 timg, tfoot = pkres.templateImage, pkres.templateFootprint
1216 if not tfoot.getBBox().isEmpty()
and tfoot.getBBox() != timg.getBBox(afwImage.PARENT):
1217 timg = timg.Factory(timg, tfoot.getBBox(), afwImage.PARENT,
True)
1218 pkres.setTemplate(timg, tfoot)
1222 """Weight the templates to best fit the observed image in each filter 1224 This function re-weights the templates so that their linear combination best represents 1225 the observed image in that filter. 1226 In the future it may be useful to simultaneously weight all of the filters together. 1230 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1231 Container for the final deblender results. 1233 LSST logger for logging purposes. 1238 ``weightTemplates`` does not actually modify the ``Footprint`` templates other than 1239 to add a weight to them, so ``modified`` is always ``False``. 1242 log.trace(
'Weighting templates')
1243 for fidx
in debResult.filters:
1244 _weightTemplates(debResult.deblendedParents[fidx])
1247 def _weightTemplates(dp):
1248 """Weight the templates to best match the parent Footprint in a single filter 1250 This includes weighting both regular templates and point source templates 1254 dp: `DeblendedParent` 1255 The deblended parent to re-weight 1261 nchild = np.sum([pkres.skip
is False for pkres
in dp.peaks])
1262 A = np.zeros((dp.W*dp.H, nchild))
1263 parentImage = afwImage.ImageF(dp.bb)
1264 afwDet.copyWithinFootprintImage(dp.fp, dp.img, parentImage)
1265 b = parentImage.getArray().ravel()
1268 for pkres
in dp.peaks:
1271 childImage = afwImage.ImageF(dp.bb)
1272 afwDet.copyWithinFootprintImage(dp.fp, pkres.templateImage, childImage)
1273 A[:, index] = childImage.getArray().ravel()
1276 X1, r1, rank1, s1 = np.linalg.lstsq(A, b, rcond=-1)
1281 for pkres
in dp.peaks:
1284 pkres.templateImage *= X1[index]
1285 pkres.setTemplateWeight(X1[index])
1289 """Remove "degenerate templates" 1291 If galaxies have substructure, such as face-on spirals, the process of identifying peaks can 1292 "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite 1293 similar because they represent the same galaxy, so we try to identify these "degenerate" peaks 1294 by looking at the inner product (in pixel space) of pairs of templates. 1295 If they are nearly parallel, we only keep one of the peaks and reject the other. 1296 If only one of the peaks is a PSF template, the other template is used, 1297 otherwise the one with the maximum template value is kept. 1301 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1302 Container for the final deblender results. 1304 LSST logger for logging purposes. 1305 maxTempDotProd: `float`, optional 1306 All dot products between templates greater than ``maxTempDotProd`` will result in one 1307 of the templates removed. 1312 If any degenerate templates are found, ``modified`` is ``True``. 1314 log.trace(
'Looking for degnerate templates')
1317 for fidx
in debResult.filters:
1318 dp = debResult.deblendedParents[fidx]
1319 nchild = np.sum([pkres.skip
is False for pkres
in dp.peaks])
1320 indexes = [pkres.pki
for pkres
in dp.peaks
if pkres.skip
is False]
1325 A = np.zeros((nchild, nchild))
1328 for pkres
in dp.peaks:
1331 heavies.append(afwDet.makeHeavyFootprint(pkres.templateFootprint,
1332 afwImage.MaskedImageF(pkres.templateImage)))
1333 maxTemplate.append(np.max(pkres.templateImage.getArray()))
1335 for i
in range(nchild):
1336 for j
in range(i + 1):
1337 A[i, j] = heavies[i].dot(heavies[j])
1340 for i
in range(nchild):
1342 norm = A[i, i]*A[j, j]
1346 A[i, j] /= np.sqrt(norm)
1351 for i
in range(nchild):
1354 if A[i, j] > currentMax:
1355 currentMax = A[i, j]
1356 if currentMax > maxTempDotProd:
1369 reject = indexes[rejectedIndex]
1371 if dp.peaks[keep].deblendedAsPsf
and dp.peaks[reject].deblendedAsPsf
is False:
1372 keep = indexes[rejectedIndex]
1374 elif dp.peaks[keep].deblendedAsPsf
is False and dp.peaks[reject].deblendedAsPsf:
1375 reject = indexes[rejectedIndex]
1378 if maxTemplate[rejectedIndex] > maxTemplate[i]:
1379 keep = indexes[rejectedIndex]
1381 log.trace(
'Removing object with index %d : %f. Degenerate with %d' % (reject, currentMax,
1383 dp.peaks[reject].skip =
True 1384 dp.peaks[reject].degenerate =
True 1388 def apportionFlux(debResult, log, assignStrayFlux=True, strayFluxAssignment='r-to-peak',
1389 strayFluxToPointSources='necessary', clipStrayFluxFraction=0.001,
1390 getTemplateSum=False):
1391 """Apportion flux to all of the peak templates in each filter 1393 Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of 1394 flux assigned to each ``template``. 1395 Leftover "stray flux" is assigned to peaks based on the other parameters. 1399 debResult: `lsst.meas.deblender.baseline.DeblenderResult` 1400 Container for the final deblender results. 1402 LSST logger for logging purposes. 1403 assignStrayFlux: `bool`, optional 1404 If True then flux in the parent footprint that is not covered by any of the 1405 template footprints is assigned to templates based on their 1/(1+r^2) distance. 1406 How the flux is apportioned is determined by ``strayFluxAssignment``. 1407 strayFluxAssignment: `string`, optional 1408 Determines how stray flux is apportioned. 1409 * ``trim``: Trim stray flux and do not include in any footprints 1410 * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks 1411 * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the 1412 minimum distance from the stray flux to footprint 1413 * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan) 1414 distance to the stray flux 1415 strayFluxToPointSources: `string`, optional 1416 Determines how stray flux is apportioned to point sources 1417 * ``never``: never apportion stray flux to point sources 1418 * ``necessary`` (default): point sources are included only if there are no extended sources nearby 1419 * ``always``: point sources are always included in the 1/(1+r^2) splitting 1420 clipStrayFluxFraction: `float`, optional 1421 Minimum stray-flux portion. 1422 Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero. 1423 getTemplateSum: `bool`, optional 1424 As part of the flux calculation, the sum of the templates is calculated. 1425 If ``getTemplateSum==True`` then the sum of the templates is stored in the result 1426 (a `DeblendedFootprint`). 1431 Apportion flux always modifies the templates, so ``modified`` is always ``True``. 1432 However, this should likely be the final step and it is unlikely that 1433 any deblender plugins will be re-run. 1435 validStrayPtSrc = [
'never',
'necessary',
'always']
1436 validStrayAssign = [
'r-to-peak',
'r-to-footprint',
'nearest-footprint',
'trim']
1437 if strayFluxToPointSources
not in validStrayPtSrc:
1438 raise ValueError(((
'strayFluxToPointSources: value \"%s\" not in the set of allowed values: ') %
1439 strayFluxToPointSources) + str(validStrayPtSrc))
1440 if strayFluxAssignment
not in validStrayAssign:
1441 raise ValueError(((
'strayFluxAssignment: value \"%s\" not in the set of allowed values: ') %
1442 strayFluxAssignment) + str(validStrayAssign))
1444 for fidx
in debResult.filters:
1445 dp = debResult.deblendedParents[fidx]
1458 bb = dp.fp.getBBox()
1460 for peaki, pkres
in enumerate(dp.peaks):
1463 tmimgs.append(pkres.templateImage)
1464 tfoots.append(pkres.templateFootprint)
1466 dpsf.append(pkres.deblendedAsPsf)
1468 pkx.append(pk.getIx())
1469 pky.append(pk.getIy())
1470 ibi.append(pkres.pki)
1473 log.trace(
'Apportioning flux among %i templates', len(tmimgs))
1474 sumimg = afwImage.ImageF(bb)
1479 if strayFluxAssignment ==
'trim':
1480 assignStrayFlux =
False 1481 strayopts |= butils.STRAYFLUX_TRIM
1483 strayopts |= butils.ASSIGN_STRAYFLUX
1484 if strayFluxToPointSources ==
'necessary':
1485 strayopts |= butils.STRAYFLUX_TO_POINT_SOURCES_WHEN_NECESSARY
1486 elif strayFluxToPointSources ==
'always':
1487 strayopts |= butils.STRAYFLUX_TO_POINT_SOURCES_ALWAYS
1489 if strayFluxAssignment ==
'r-to-peak':
1492 elif strayFluxAssignment ==
'r-to-footprint':
1493 strayopts |= butils.STRAYFLUX_R_TO_FOOTPRINT
1494 elif strayFluxAssignment ==
'nearest-footprint':
1495 strayopts |= butils.STRAYFLUX_NEAREST_FOOTPRINT
1497 portions, strayflux = butils.apportionFlux(dp.maskedImage, dp.fp, tmimgs, tfoots, sumimg, dpsf,
1498 pkx, pky, strayopts, clipStrayFluxFraction)
1501 if strayFluxAssignment ==
'trim':
1504 finalSpanSet = finalSpanSet.union(foot.spans)
1505 dp.fp.setSpans(finalSpanSet)
1509 debResult.setTemplateSums(sumimg, fidx)
1513 for j, (pk, pkres)
in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1516 pkres.setFluxPortion(portions[ii])
1521 stray = strayflux[ii]
1526 pkres.setStrayFlux(stray)
1529 for j, (pk, pkres)
in enumerate(zip(dp.fp.getPeaks(), dp.peaks)):
1533 for foot, add
in [(pkres.templateFootprint,
True), (pkres.origFootprint,
True),
1534 (pkres.strayFlux,
False)]:
1537 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)