24 from scipy
import ndimage
32 from .assembleCoadd
import AssembleCoaddTask, CompareWarpAssembleCoaddTask, CompareWarpAssembleCoaddConfig
34 __all__ = [
"DcrAssembleCoaddTask",
"DcrAssembleCoaddConfig"]
38 dcrNumSubfilters = pexConfig.Field(
40 doc=
"Number of sub-filters to forward model chromatic effects to fit the supplied exposures.",
43 maxNumIter = pexConfig.Field(
45 doc=
"Maximum number of iterations of forward modeling.",
48 minNumIter = pexConfig.Field(
50 doc=
"Minimum number of iterations of forward modeling.",
53 convergenceThreshold = pexConfig.Field(
55 doc=
"Target relative change in convergence between iterations of forward modeling.",
58 useConvergence = pexConfig.Field(
60 doc=
"Use convergence test as a forward modeling end condition?" 61 "If not set, skips calculating convergence and runs for ``maxNumIter`` iterations",
64 baseGain = pexConfig.Field(
66 doc=
"Relative weight to give the new solution vs. the last solution when updating the model." 67 "A value of 1.0 gives equal weight to both solutions." 68 "Small values imply slower convergence of the solution, but can " 69 "help prevent overshooting and failures in the fit." 70 "If ``baseGain`` is None, a conservative gain " 71 "will be calculated from the number of subfilters. ",
74 useProgressiveGain = pexConfig.Field(
76 doc=
"Use a gain that slowly increases above ``baseGain`` to accelerate convergence? " 77 "When calculating the next gain, we use up to 5 previous gains and convergence values." 78 "Can be set to False to force the model to change at the rate of ``baseGain``. ",
81 doAirmassWeight = pexConfig.Field(
83 doc=
"Weight exposures by airmass? Useful if there are relatively few high-airmass observations.",
86 modelWeightsWidth = pexConfig.Field(
88 doc=
"Width of the region around detected sources to include in the DcrModel.",
91 useModelWeights = pexConfig.Field(
93 doc=
"Width of the region around detected sources to include in the DcrModel.",
96 splitSubfilters = pexConfig.Field(
98 doc=
"Calculate DCR for two evenly-spaced wavelengths in each subfilter." 99 "Instead of at the midpoint",
102 regularizeModelIterations = pexConfig.Field(
104 doc=
"Maximum relative change of the model allowed between iterations." 105 "Set to zero to disable.",
108 regularizeModelFrequency = pexConfig.Field(
110 doc=
"Maximum relative change of the model allowed between subfilters." 111 "Set to zero to disable.",
114 convergenceMaskPlanes = pexConfig.ListField(
116 default=[
"DETECTED"],
117 doc=
"Mask planes to use to calculate convergence." 119 regularizationWidth = pexConfig.Field(
122 doc=
"Minimum radius of a region to include in regularization, in pixels." 124 imageWarpMethod = pexConfig.Field(
126 doc=
"Name of the warping kernel to use for shifting the image and variance planes.",
129 maskWarpMethod = pexConfig.Field(
131 doc=
"Name of the warping kernel to use for shifting the mask plane.",
136 CompareWarpAssembleCoaddConfig.setDefaults(self)
145 """Assemble DCR coadded images from a set of warps. 150 The number of pixels to grow each subregion by to allow for DCR. 151 warpCtrl : `lsst.afw.math.WarpingControl` 152 Configuration settings for warping an image 156 As with AssembleCoaddTask, we want to assemble a coadded image from a set of 157 Warps (also called coadded temporary exposures), including the effects of 158 Differential Chromatic Refraction (DCR). 159 For full details of the mathematics and algorithm, please see 160 DMTN-037: DCR-matched template generation (https://dmtn-037.lsst.io). 162 This Task produces a DCR-corrected deepCoadd, as well as a dcrCoadd for 163 each subfilter used in the iterative calculation. 164 It begins by dividing the bandpass-defining filter into N equal bandwidth 165 "subfilters", and divides the flux in each pixel from an initial coadd 166 equally into each as a "dcrModel". Because the airmass and parallactic 167 angle of each individual exposure is known, we can calculate the shift 168 relative to the center of the band in each subfilter due to DCR. For each 169 exposure we apply this shift as a linear transformation to the dcrModels 170 and stack the results to produce a DCR-matched exposure. The matched 171 exposures are subtracted from the input exposures to produce a set of 172 residual images, and these residuals are reverse shifted for each 173 exposures' subfilters and stacked. The shifted and stacked residuals are 174 added to the dcrModels to produce a new estimate of the flux in each pixel 175 within each subfilter. The dcrModels are solved for iteratively, which 176 continues until the solution from a new iteration improves by less than 177 a set percentage, or a maximum number of iterations is reached. 178 Two forms of regularization are employed to reduce unphysical results. 179 First, the new solution is averaged with the solution from the previous 180 iteration, which mitigates oscillating solutions where the model 181 overshoots with alternating very high and low values. 182 Second, a common degeneracy when the data have a limited range of airmass or 183 parallactic angle values is for one subfilter to be fit with very low or 184 negative values, while another subfilter is fit with very high values. This 185 typically appears in the form of holes next to sources in one subfilter, 186 and corresponding extended wings in another. Because each subfilter has 187 a narrow bandwidth we assume that physical sources that are above the noise 188 level will not vary in flux by more than a factor of `frequencyClampFactor` 189 between subfilters, and pixels that have flux deviations larger than that 190 factor will have the excess flux distributed evenly among all subfilters. 193 ConfigClass = DcrAssembleCoaddConfig
194 _DefaultName =
"dcrAssembleCoadd" 198 """Assemble a coadd from a set of warps. 200 Coadd a set of Warps. Compute weights to be applied to each Warp and 201 find scalings to match the photometric zeropoint to a reference Warp. 202 Assemble the Warps using run method. 203 Forward model chromatic effects across multiple subfilters, 204 and subtract from the input Warps to build sets of residuals. 205 Use the residuals to construct a new ``DcrModel`` for each subfilter, 206 and iterate until the model converges. 207 Interpolate over NaNs and optionally write the coadd to disk. 208 Return the coadded exposure. 212 dataRef : `lsst.daf.persistence.ButlerDataRef` 213 Data reference defining the patch for coaddition and the 215 selectDataList : `list` of `lsst.daf.persistence.ButlerDataRef` 216 List of data references to warps. Data to be coadded will be 217 selected from this list based on overlap with the patch defined by 222 results : `lsst.pipe.base.Struct` 223 The Struct contains the following fields: 225 - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`) 226 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`) 227 - ``dcrCoadds``: `list` of coadded exposures for each subfilter 228 - ``dcrNImages``: `list` of exposure count images for each subfilter 230 results = AssembleCoaddTask.runDataRef(self, dataRef, selectDataList=selectDataList)
231 for subfilter
in range(self.config.dcrNumSubfilters):
233 if self.config.doWrite:
234 self.log.info(
"Persisting dcrCoadd")
235 dataRef.put(results.dcrCoadds[subfilter],
"dcrCoadd", subfilter=subfilter,
236 numSubfilters=self.config.dcrNumSubfilters)
237 if self.config.doNImage
and results.dcrNImages
is not None:
238 dataRef.put(results.dcrNImages[subfilter],
"dcrCoadd_nImage", subfilter=subfilter,
239 numSubfilters=self.config.dcrNumSubfilters)
244 """Prepare the DCR coadd by iterating through the visitInfo of the input warps. 246 Sets the properties ``warpCtrl`` and ``bufferSize``. 250 templateCoadd : `lsst.afw.image.ExposureF` 251 The initial coadd exposure before accounting for DCR. 252 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 253 The data references to the input warped exposures. 254 weightList : `list` of `float` 255 The weight to give each input exposure in the coadd 256 Will be modified in place if ``doAirmassWeight`` is set. 260 dcrModels : `lsst.pipe.tasks.DcrModel` 261 Best fit model of the true sky after correcting chromatic effects. 266 If ``lambdaMin`` is missing from the Mapper class of the obs package being used. 268 filterInfo = templateCoadd.getFilter()
269 if np.isnan(filterInfo.getFilterProperty().getLambdaMin()):
270 raise NotImplementedError(
"No minimum/maximum wavelength information found" 271 " in the filter definition! Please add lambdaMin and lambdaMax" 272 " to the Mapper class in your obs package.")
275 for visitNum, tempExpRef
in enumerate(tempExpRefList):
276 visitInfo = tempExpRef.get(tempExpName +
"_visitInfo")
277 airmass = visitInfo.getBoresightAirmass()
278 if self.config.doAirmassWeight:
279 weightList[visitNum] *= airmass
280 dcrShifts.append(np.max(np.abs(calculateDcr(visitInfo, templateCoadd.getWcs(),
281 filterInfo, self.config.dcrNumSubfilters))))
286 warpInterpLength = max(self.config.subregionSize)
287 self.
warpCtrl = afwMath.WarpingControl(self.config.imageWarpMethod,
288 self.config.maskWarpMethod,
289 cacheSize=warpCache, interpLength=warpInterpLength)
290 dcrModels = DcrModel.fromImage(templateCoadd.maskedImage,
291 self.config.dcrNumSubfilters,
292 filterInfo=filterInfo,
293 psf=templateCoadd.getPsf())
296 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
297 supplementaryData=None):
298 """Assemble the coadd. 300 Requires additional inputs Struct ``supplementaryData`` to contain a 301 ``templateCoadd`` that serves as the model of the static sky. 303 Find artifacts and apply them to the warps' masks creating a list of 304 alternative masks with a new "CLIPPED" plane and updated "NO_DATA" plane 305 Then pass these alternative masks to the base class's assemble method. 307 Divide the ``templateCoadd`` evenly between each subfilter of a 308 ``DcrModel`` as the starting best estimate of the true wavelength- 309 dependent sky. Forward model the ``DcrModel`` using the known 310 chromatic effects in each subfilter and calculate a convergence metric 311 based on how well the modeled template matches the input warps. If 312 the convergence has not yet reached the desired threshold, then shift 313 and stack the residual images to build a new ``DcrModel``. Apply 314 conditioning to prevent oscillating solutions between iterations or 317 Once the ``DcrModel`` reaches convergence or the maximum number of 318 iterations has been reached, fill the metadata for each subfilter 319 image and make them proper ``coaddExposure``s. 323 skyInfo : `lsst.pipe.base.Struct` 324 Patch geometry information, from getSkyInfo 325 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 326 The data references to the input warped exposures. 327 imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 328 The image scalars correct for the zero point of the exposures. 329 weightList : `list` of `float` 330 The weight to give each input exposure in the coadd 331 supplementaryData : `lsst.pipe.base.Struct` 332 Result struct returned by ``makeSupplementaryData`` with components: 334 - ``templateCoadd``: coadded exposure (`lsst.afw.image.Exposure`) 338 result : `lsst.pipe.base.Struct` 339 Result struct with components: 341 - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`) 342 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`) 343 - ``dcrCoadds``: `list` of coadded exposures for each subfilter 344 - ``dcrNImages``: `list` of exposure count images for each subfilter 346 templateCoadd = supplementaryData.templateCoadd
347 spanSetMaskList = self.
findArtifacts(templateCoadd, tempExpRefList, imageScalerList)
348 badMaskPlanes = self.config.badMaskPlanes[:]
349 badMaskPlanes.append(
"CLIPPED")
350 badPixelMask = templateCoadd.mask.getPlaneBitMask(badMaskPlanes)
356 dcrModels = self.
prepareDcrInputs(templateCoadd, tempExpRefList, weightList)
357 if self.config.doNImage:
359 tempExpRefList, spanSetMaskList, stats.ctrl)
360 nImage = afwImage.ImageU(skyInfo.bbox)
364 for dcrNImage
in dcrNImages:
369 baseMask = templateCoadd.mask
370 subregionSize = afwGeom.Extent2I(*self.config.subregionSize)
371 for subBBox
in self.
_subBBoxIter(skyInfo.bbox, subregionSize):
373 self.log.info(
"Computing coadd over %s", subBBox)
374 dcrBBox = afwGeom.Box2I(subBBox)
376 dcrBBox.clip(dcrModels.bbox)
377 if self.config.useModelWeights:
382 imageScalerList, weightList, spanSetMaskList,
384 self.log.info(
"Initial convergence : %s", convergenceMetric)
385 convergenceList = [convergenceMetric]
387 convergenceCheck = 1.
388 subfilterVariance =
None 389 while (convergenceCheck > self.config.convergenceThreshold
or 390 modelIter < self.config.minNumIter):
393 weightList, spanSetMaskList, stats.flags, stats.ctrl,
394 convergenceMetric, baseMask, subfilterVariance, gain,
396 if self.config.useConvergence:
398 imageScalerList, weightList,
401 convergenceCheck = (convergenceList[-1] - convergenceMetric)/convergenceMetric
402 if convergenceCheck < 0:
403 self.log.warn(
"Coadd %s diverged before reaching maximum iterations or" 404 " desired convergence improvement of %s." 406 subBBox, self.config.convergenceThreshold, convergenceCheck)
408 convergenceList.append(convergenceMetric)
409 if modelIter > self.config.maxNumIter:
410 if self.config.useConvergence:
411 self.log.warn(
"Coadd %s reached maximum iterations before reaching" 412 " desired convergence improvement of %s." 413 " Final convergence improvement: %s",
414 subBBox, self.config.convergenceThreshold, convergenceCheck)
417 if self.config.useConvergence:
418 self.log.info(
"Iteration %s with convergence metric %s, %.4f%% improvement (gain: %.2f)",
419 modelIter, convergenceMetric, 100.*convergenceCheck, gain)
422 if self.config.useConvergence:
423 self.log.info(
"Coadd %s finished with convergence metric %s after %s iterations",
424 subBBox, convergenceMetric, modelIter)
426 self.log.info(
"Coadd %s finished after %s iterations", subBBox, modelIter)
427 if self.config.useConvergence:
428 self.log.info(
"Final convergence improvement was %.4f%% overall",
429 100*(convergenceList[0] - convergenceMetric)/convergenceMetric)
431 dcrCoadds = self.
fillCoadd(dcrModels, skyInfo, tempExpRefList, weightList,
432 calibration=self.scaleZeroPoint.getCalib(),
433 coaddInputs=self.inputRecorder.makeCoaddInputs(),
434 mask=templateCoadd.mask)
436 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
437 dcrCoadds=dcrCoadds, dcrNImages=dcrNImages)
439 def calculateNImage(self, dcrModels, bbox, tempExpRefList, spanSetMaskList, statsCtrl):
440 """Calculate the number of exposures contributing to each subfilter. 444 dcrModels : `lsst.pipe.tasks.DcrModel` 445 Best fit model of the true sky after correcting chromatic effects. 446 bbox : `lsst.afw.geom.box.Box2I` 447 Bounding box of the patch to coadd. 448 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 449 The data references to the input warped exposures. 450 spanSetMaskList : `list` of `dict` containing spanSet lists, or None 451 Each element is dict with keys = mask plane name to add the spans to 452 statsCtrl : `lsst.afw.math.StatisticsControl` 453 Statistics control object for coadd 457 dcrNImages : `list` of `lsst.afw.image.ImageU` 458 List of exposure count images for each subfilter 460 dcrNImages = [afwImage.ImageU(bbox)
for subfilter
in range(self.config.dcrNumSubfilters)]
462 for tempExpRef, altMaskSpans
in zip(tempExpRefList, spanSetMaskList):
463 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
464 visitInfo = exposure.getInfo().getVisitInfo()
465 wcs = exposure.getInfo().getWcs()
467 if altMaskSpans
is not None:
469 dcrShift = calculateDcr(visitInfo, wcs, dcrModels.filter, self.config.dcrNumSubfilters)
470 for dcr, dcrNImage
in zip(dcrShift, dcrNImages):
471 shiftedImage = applyDcr(exposure.maskedImage, dcr, self.
warpCtrl, useInverse=
True)
472 dcrNImage.array[shiftedImage.mask.array & statsCtrl.getAndMask() == 0] += 1
475 def dcrAssembleSubregion(self, dcrModels, bbox, dcrBBox, tempExpRefList, imageScalerList, weightList,
476 spanSetMaskList, statsFlags, statsCtrl, convergenceMetric,
477 baseMask, subfilterVariance, gain, modelWeights):
478 """Assemble the DCR coadd for a sub-region. 480 Build a DCR-matched template for each input exposure, then shift the 481 residuals according to the DCR in each subfilter. 482 Stack the shifted residuals and apply them as a correction to the 483 solution from the previous iteration. 484 Restrict the new model solutions from varying by more than a factor of 485 `modelClampFactor` from the last solution, and additionally restrict the 486 individual subfilter models from varying by more than a factor of 487 `frequencyClampFactor` from their average. 488 Finally, mitigate potentially oscillating solutions by averaging the new 489 solution with the solution from the previous iteration, weighted by 490 their convergence metric. 494 dcrModels : `lsst.pipe.tasks.DcrModel` 495 Best fit model of the true sky after correcting chromatic effects. 496 bbox : `lsst.afw.geom.box.Box2I` 497 Bounding box of the subregion to coadd. 498 dcrBBox :`lsst.afw.geom.box.Box2I` 499 Sub-region of the coadd which includes a buffer to allow for DCR. 500 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 501 The data references to the input warped exposures. 502 imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 503 The image scalars correct for the zero point of the exposures. 504 weightList : `list` of `float` 505 The weight to give each input exposure in the coadd 506 spanSetMaskList : `list` of `dict` containing spanSet lists, or None 507 Each element is dict with keys = mask plane name to add the spans to 508 statsFlags : `lsst.afw.math.Property` 509 Statistics settings for coaddition. 510 statsCtrl : `lsst.afw.math.StatisticsControl` 511 Statistics control object for coadd 512 convergenceMetric : `float` 513 Quality of fit metric for the matched templates of the input images. 514 baseMask : `lsst.afw.image.Mask` 515 Mask of the initial template coadd. 516 subfilterVariance : `list` of `numpy.ndarray` 517 The variance of each coadded subfilter image. 518 gain : `float`, optional 519 Relative weight to give the new solution when updating the model. 520 modelWeights : `numpy.ndarray` or `float` 521 A 2D array of weight values that tapers smoothly to zero away from detected sources. 522 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 525 residualGeneratorList = []
527 for tempExpRef, imageScaler, altMaskSpans
in zip(tempExpRefList, imageScalerList, spanSetMaskList):
528 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=dcrBBox)
529 visitInfo = exposure.getInfo().getVisitInfo()
530 wcs = exposure.getInfo().getWcs()
531 maskedImage = exposure.maskedImage
532 templateImage = dcrModels.buildMatchedTemplate(warpCtrl=self.
warpCtrl, visitInfo=visitInfo,
533 bbox=dcrBBox, wcs=wcs, mask=baseMask,
534 splitSubfilters=self.config.splitSubfilters)
535 imageScaler.scaleMaskedImage(maskedImage)
536 if altMaskSpans
is not None:
539 if self.config.removeMaskPlanes:
541 maskedImage -= templateImage
542 maskedImage.image.array *= modelWeights
543 residualGeneratorList.append(self.
dcrResiduals(maskedImage, visitInfo, dcrBBox, wcs,
547 statsFlags, statsCtrl, weightList,
548 mask=baseMask, gain=gain)
549 dcrModels.assign(dcrSubModelOut, bbox)
552 """Prepare a residual image for stacking in each subfilter by applying the reverse DCR shifts. 556 residual : `lsst.afw.image.MaskedImageF` 557 The residual masked image for one exposure, 558 after subtracting the matched template 559 visitInfo : `lsst.afw.image.VisitInfo` 560 Metadata for the exposure. 561 bbox : `lsst.afw.geom.box.Box2I` 562 Sub-region of the coadd 563 wcs : `lsst.afw.geom.SkyWcs` 564 Coordinate system definition (wcs) for the exposure. 565 filterInfo : `lsst.afw.image.Filter` 566 The filter definition, set in the current instruments' obs package. 567 Required for any calculation of DCR, including making matched templates. 571 residualImage : `lsst.afw.image.maskedImageF` 572 The residual image for the next subfilter, shifted for DCR. 574 dcrShift = calculateDcr(visitInfo, wcs, filterInfo, self.config.dcrNumSubfilters)
576 yield applyDcr(residual, dcr, self.
warpCtrl, bbox=bbox, useInverse=
True)
579 statsFlags, statsCtrl, weightList,
581 """Calculate a new DcrModel from a set of image residuals. 585 dcrModels : `lsst.pipe.tasks.DcrModel` 586 Current model of the true sky after correcting chromatic effects. 587 residualGeneratorList : `generator` of `lsst.afw.image.maskedImageF` 588 The residual image for the next subfilter, shifted for DCR. 589 bbox : `lsst.afw.geom.box.Box2I` 590 Sub-region of the coadd 591 statsFlags : `lsst.afw.math.Property` 592 Statistics settings for coaddition. 593 statsCtrl : `lsst.afw.math.StatisticsControl` 594 Statistics control object for coadd 595 weightList : `list` of `float` 596 The weight to give each input exposure in the coadd 597 mask : `lsst.afw.image.Mask` 598 Mask to use for each new model image. 600 Relative weight to give the new solution when updating the model. 604 dcrModel : `lsst.pipe.tasks.DcrModel` 605 New model of the true sky after correcting chromatic effects. 608 clipped = dcrModels.mask.getPlaneBitMask(
"CLIPPED")
610 for subfilter, model
in enumerate(dcrModels):
611 residualsList = [next(residualGenerator)
for residualGenerator
in residualGeneratorList]
612 residual = afwMath.statisticsStack(residualsList, statsFlags, statsCtrl, weightList,
614 residual.setXY0(bbox.getBegin())
616 residual += model[bbox]
619 badPixels = ~np.isfinite(newModel.image.array)
622 newModel.setMask(mask[bbox])
623 newModel.image.array[badPixels] = model[bbox].image.array[badPixels]
624 if self.config.regularizeModelIterations > 0:
625 dcrModels.regularizeModelIter(subfilter, newModel, bbox,
626 self.config.regularizeModelIterations,
627 self.config.regularizationWidth)
628 newModelImages.append(newModel)
629 if self.config.regularizeModelFrequency > 0:
630 dcrModels.regularizeModelFreq(newModelImages, bbox,
631 self.config.regularizeModelFrequency,
632 self.config.regularizationWidth)
633 dcrModels.conditionDcrModel(newModelImages, bbox, gain=gain)
634 return DcrModel(newModelImages, dcrModels.filter, dcrModels.psf)
637 weightList, spanSetMaskList, statsCtrl):
638 """Calculate a quality of fit metric for the matched templates. 642 dcrModels : `lsst.pipe.tasks.DcrModel` 643 Best fit model of the true sky after correcting chromatic effects. 644 bbox : `lsst.afw.geom.box.Box2I` 646 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 647 The data references to the input warped exposures. 648 imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 649 The image scalars correct for the zero point of the exposures. 650 weightList : `list` of `float` 651 The weight to give each input exposure in the coadd 652 spanSetMaskList : `list` of `dict` containing spanSet lists, or None 653 Each element is dict with keys = mask plane name to add the spans to 654 statsCtrl : `lsst.afw.math.StatisticsControl` 655 Statistics control object for coadd 659 convergenceMetric : `float` 660 Quality of fit metric for all input exposures, within the sub-region 662 significanceImage = np.abs(dcrModels.getReferenceImage(bbox))
664 significanceImage += nSigma*dcrModels.calculateNoiseCutoff(dcrModels[1], statsCtrl,
670 zipIterables = zip(tempExpRefList, weightList, imageScalerList, spanSetMaskList)
671 for tempExpRef, expWeight, imageScaler, altMaskSpans
in zipIterables:
672 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
673 imageScaler.scaleMaskedImage(exposure.maskedImage)
675 altMaskSpans=altMaskSpans)
676 metric += singleMetric*expWeight
677 metricList[tempExpRef.dataId[
"visit"]] = singleMetric
679 self.log.info(
"Individual metrics:\n%s", metricList)
680 return 1.0
if weight == 0.0
else metric/weight
683 statsCtrl, altMaskSpans=None):
684 """Calculate a quality of fit metric for a single matched template. 688 dcrModels : `lsst.pipe.tasks.DcrModel` 689 Best fit model of the true sky after correcting chromatic effects. 690 exposure : `lsst.afw.image.ExposureF` 691 The input warped exposure to evaluate. 692 significanceImage : `numpy.ndarray` 693 Array of weights for each pixel corresponding to its significance 694 for the convergence calculation. 695 statsCtrl : `lsst.afw.math.StatisticsControl` 696 Statistics control object for coadd 697 altMaskSpans : `dict` containing spanSet lists, or None 698 The keys of the `dict` equal the mask plane name to add the spans to 702 convergenceMetric : `float` 703 Quality of fit metric for one exposure, within the sub-region. 705 convergeMask = exposure.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
706 templateImage = dcrModels.buildMatchedTemplate(warpCtrl=self.
warpCtrl,
707 visitInfo=exposure.getInfo().getVisitInfo(),
708 bbox=exposure.getBBox(),
709 wcs=exposure.getInfo().getWcs())
710 diffVals = np.abs(exposure.image.array - templateImage.image.array)*significanceImage
711 refVals = np.abs(templateImage.image.array)*significanceImage
713 finitePixels = np.isfinite(diffVals)
714 if altMaskSpans
is not None:
716 goodMaskPixels = exposure.mask.array & statsCtrl.getAndMask() == 0
717 convergeMaskPixels = exposure.mask.array & convergeMask > 0
718 usePixels = finitePixels & goodMaskPixels & convergeMaskPixels
719 if np.sum(usePixels) == 0:
722 diffUse = diffVals[usePixels]
723 refUse = refVals[usePixels]
724 metric = np.sum(diffUse/np.median(diffUse))/np.sum(refUse/np.median(diffUse))
728 """Add a list of sub-band coadds together. 732 dcrCoadds : `list` of `lsst.afw.image.ExposureF` 733 A list of coadd exposures, each exposure containing 734 the model for one subfilter. 738 coaddExposure : `lsst.afw.image.ExposureF` 739 A single coadd exposure that is the sum of the sub-bands. 741 coaddExposure = dcrCoadds[0].clone()
742 for coadd
in dcrCoadds[1:]:
743 coaddExposure.maskedImage += coadd.maskedImage
746 def fillCoadd(self, dcrModels, skyInfo, tempExpRefList, weightList, calibration=None, coaddInputs=None,
748 """Create a list of coadd exposures from a list of masked images. 752 dcrModels : `lsst.pipe.tasks.DcrModel` 753 Best fit model of the true sky after correcting chromatic effects. 754 skyInfo : `lsst.pipe.base.Struct` 755 Patch geometry information, from getSkyInfo 756 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 757 The data references to the input warped exposures. 758 weightList : `list` of `float` 759 The weight to give each input exposure in the coadd 760 calibration : `lsst.afw.Image.Calib`, optional 761 Scale factor to set the photometric zero point of an exposure. 762 coaddInputs : `lsst.afw.Image.CoaddInputs`, optional 763 A record of the observations that are included in the coadd. 764 mask : `lsst.afw.image.Mask`, optional 765 Optional mask to override the values in the final coadd. 769 dcrCoadds : `list` of `lsst.afw.image.ExposureF` 770 A list of coadd exposures, each exposure containing 771 the model for one subfilter. 774 for model
in dcrModels:
775 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
776 if calibration
is not None:
777 coaddExposure.setCalib(calibration)
778 if coaddInputs
is not None:
779 coaddExposure.getInfo().setCoaddInputs(coaddInputs)
782 coaddUtils.setCoaddEdgeBits(model[skyInfo.bbox].mask, model[skyInfo.bbox].variance)
783 coaddExposure.setMaskedImage(model[skyInfo.bbox])
785 coaddExposure.setMask(mask)
786 dcrCoadds.append(coaddExposure)
790 """Calculate the gain to use for the current iteration. 792 After calculating a new DcrModel, each value is averaged with the 793 value in the corresponding pixel from the previous iteration. This 794 reduces oscillating solutions that iterative techniques are plagued by, 795 and speeds convergence. By far the biggest changes to the model 796 happen in the first couple iterations, so we can also use a more 797 aggressive gain later when the model is changing slowly. 801 convergenceList : `list` of `float` 802 The quality of fit metric from each previous iteration. 803 gainList : `list` of `float` 804 The gains used in each previous iteration: appended with the new 806 Gains are numbers between ``self.config.baseGain`` and 1. 811 Relative weight to give the new solution when updating the model. 812 A value of 1.0 gives equal weight to both solutions. 817 If ``len(convergenceList) != len(gainList)+1``. 819 nIter = len(convergenceList)
820 if nIter != len(gainList) + 1:
821 raise ValueError(
"convergenceList (%d) must be one element longer than gainList (%d)." 822 % (len(convergenceList), len(gainList)))
824 if self.config.baseGain
is None:
827 baseGain = 1./(self.config.dcrNumSubfilters - 1)
829 baseGain = self.config.baseGain
831 if self.config.useProgressiveGain
and nIter > 2:
839 estFinalConv = [((1 + gainList[i])*convergenceList[i + 1] - convergenceList[i])/gainList[i]
840 for i
in range(nIter - 1)]
843 estFinalConv = np.array(estFinalConv)
844 estFinalConv[estFinalConv < 0] = 0
846 estFinalConv = np.median(estFinalConv[max(nIter - 5, 0):])
847 lastGain = gainList[-1]
848 lastConv = convergenceList[-2]
849 newConv = convergenceList[-1]
854 predictedConv = (estFinalConv*lastGain + lastConv)/(1. + lastGain)
860 delta = (predictedConv - newConv)/((lastConv - estFinalConv)/(1 + lastGain))
861 newGain = 1 - abs(delta)
863 newGain = (newGain + lastGain)/2.
864 gain = max(baseGain, newGain)
867 gainList.append(gain)
871 """Build an array that smoothly tapers to 0 away from detected sources. 875 dcrModels : `lsst.pipe.tasks.DcrModel` 876 Best fit model of the true sky after correcting chromatic effects. 877 dcrBBox : `lsst.afw.geom.box.Box2I` 878 Sub-region of the coadd which includes a buffer to allow for DCR. 882 weights : `numpy.ndarray` or `float` 883 A 2D array of weight values that tapers smoothly to zero away from detected sources. 884 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 889 If ``useModelWeights`` is set and ``modelWeightsWidth`` is negative. 891 if self.config.modelWeightsWidth < 0:
892 raise ValueError(
"modelWeightsWidth must not be negative if useModelWeights is set")
893 convergeMask = dcrModels.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
894 convergeMaskPixels = dcrModels.mask.array & convergeMask > 0
895 weights = np.zeros_like(dcrModels[0][dcrBBox].image.array)
896 weights[convergeMaskPixels] = 1.
897 weights = ndimage.filters.gaussian_filter(weights, self.config.modelWeightsWidth)
898 weights /= np.max(weights)
def findArtifacts(self, templateCoadd, tempExpRefList, imageScalerList)
def runDataRef(self, dataRef, selectDataList=[])
def assembleMetadata(self, coaddExposure, tempExpRefList, weightList)
def calculateNImage(self, dcrModels, bbox, tempExpRefList, spanSetMaskList, statsCtrl)
def removeMaskPlanes(self, maskedImage)
def fillCoadd(self, dcrModels, skyInfo, tempExpRefList, weightList, calibration=None, coaddInputs=None, mask=None)
def calculateSingleConvergence(self, dcrModels, exposure, significanceImage, statsCtrl, altMaskSpans=None)
def applyAltMaskPlanes(self, mask, altMaskSpans)
def calculateConvergence(self, dcrModels, bbox, tempExpRefList, imageScalerList, weightList, spanSetMaskList, statsCtrl)
def getTempExpDatasetName(self, warpType="direct")
def prepareStats(self, mask=None)
def dcrResiduals(self, residual, visitInfo, bbox, wcs, filterInfo)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, supplementaryData=None)
def setRejectedMaskMapping(statsCtrl)
def applyAltEdgeMask(self, mask, altMaskList)
def newModelFromResidual(self, dcrModels, residualGeneratorList, bbox, statsFlags, statsCtrl, weightList, mask, gain)
def prepareDcrInputs(self, templateCoadd, tempExpRefList, weightList)
def processResults(self, coaddExposure, dataRef)
def calculateGain(self, convergenceList, gainList)
def dcrAssembleSubregion(self, dcrModels, bbox, dcrBBox, tempExpRefList, imageScalerList, weightList, spanSetMaskList, statsFlags, statsCtrl, convergenceMetric, baseMask, subfilterVariance, gain, modelWeights)
def _subBBoxIter(bbox, subregionSize)
def stackCoadd(self, dcrCoadds)
def calculateModelWeights(self, dcrModels, dcrBBox)