25 from scipy
import ndimage
33 from .assembleCoadd
import AssembleCoaddTask, CompareWarpAssembleCoaddTask, CompareWarpAssembleCoaddConfig
35 __all__ = [
"DcrAssembleCoaddTask",
"DcrAssembleCoaddConfig"]
39 dcrNumSubfilters = pexConfig.Field(
41 doc=
"Number of sub-filters to forward model chromatic effects to fit the supplied exposures.",
44 maxNumIter = pexConfig.Field(
47 doc=
"Maximum number of iterations of forward modeling.",
50 minNumIter = pexConfig.Field(
53 doc=
"Minimum number of iterations of forward modeling.",
56 convergenceThreshold = pexConfig.Field(
58 doc=
"Target relative change in convergence between iterations of forward modeling.",
61 useConvergence = pexConfig.Field(
63 doc=
"Use convergence test as a forward modeling end condition?" 64 "If not set, skips calculating convergence and runs for ``maxNumIter`` iterations",
67 baseGain = pexConfig.Field(
70 doc=
"Relative weight to give the new solution vs. the last solution when updating the model." 71 "A value of 1.0 gives equal weight to both solutions." 72 "Small values imply slower convergence of the solution, but can " 73 "help prevent overshooting and failures in the fit." 74 "If ``baseGain`` is None, a conservative gain " 75 "will be calculated from the number of subfilters. ",
78 useProgressiveGain = pexConfig.Field(
80 doc=
"Use a gain that slowly increases above ``baseGain`` to accelerate convergence? " 81 "When calculating the next gain, we use up to 5 previous gains and convergence values." 82 "Can be set to False to force the model to change at the rate of ``baseGain``. ",
85 doAirmassWeight = pexConfig.Field(
87 doc=
"Weight exposures by airmass? Useful if there are relatively few high-airmass observations.",
90 modelWeightsWidth = pexConfig.Field(
92 doc=
"Width of the region around detected sources to include in the DcrModel.",
95 useModelWeights = pexConfig.Field(
97 doc=
"Width of the region around detected sources to include in the DcrModel.",
100 splitSubfilters = pexConfig.Field(
102 doc=
"Calculate DCR for two evenly-spaced wavelengths in each subfilter." 103 "Instead of at the midpoint",
106 regularizeModelIterations = pexConfig.Field(
108 doc=
"Maximum relative change of the model allowed between iterations." 109 "Set to zero to disable.",
112 regularizeModelFrequency = pexConfig.Field(
114 doc=
"Maximum relative change of the model allowed between subfilters." 115 "Set to zero to disable.",
118 convergenceMaskPlanes = pexConfig.ListField(
120 default=[
"DETECTED"],
121 doc=
"Mask planes to use to calculate convergence." 123 regularizationWidth = pexConfig.Field(
126 doc=
"Minimum radius of a region to include in regularization, in pixels." 128 imageWarpMethod = pexConfig.Field(
130 doc=
"Name of the warping kernel to use for shifting the image and variance planes.",
133 maskWarpMethod = pexConfig.Field(
135 doc=
"Name of the warping kernel to use for shifting the mask plane.",
140 CompareWarpAssembleCoaddConfig.setDefaults(self)
153 """Assemble DCR coadded images from a set of warps. 158 The number of pixels to grow each subregion by to allow for DCR. 159 warpCtrl : `lsst.afw.math.WarpingControl` 160 Configuration settings for warping an image 164 As with AssembleCoaddTask, we want to assemble a coadded image from a set of 165 Warps (also called coadded temporary exposures), including the effects of 166 Differential Chromatic Refraction (DCR). 167 For full details of the mathematics and algorithm, please see 168 DMTN-037: DCR-matched template generation (https://dmtn-037.lsst.io). 170 This Task produces a DCR-corrected deepCoadd, as well as a dcrCoadd for 171 each subfilter used in the iterative calculation. 172 It begins by dividing the bandpass-defining filter into N equal bandwidth 173 "subfilters", and divides the flux in each pixel from an initial coadd 174 equally into each as a "dcrModel". Because the airmass and parallactic 175 angle of each individual exposure is known, we can calculate the shift 176 relative to the center of the band in each subfilter due to DCR. For each 177 exposure we apply this shift as a linear transformation to the dcrModels 178 and stack the results to produce a DCR-matched exposure. The matched 179 exposures are subtracted from the input exposures to produce a set of 180 residual images, and these residuals are reverse shifted for each 181 exposures' subfilters and stacked. The shifted and stacked residuals are 182 added to the dcrModels to produce a new estimate of the flux in each pixel 183 within each subfilter. The dcrModels are solved for iteratively, which 184 continues until the solution from a new iteration improves by less than 185 a set percentage, or a maximum number of iterations is reached. 186 Two forms of regularization are employed to reduce unphysical results. 187 First, the new solution is averaged with the solution from the previous 188 iteration, which mitigates oscillating solutions where the model 189 overshoots with alternating very high and low values. 190 Second, a common degeneracy when the data have a limited range of airmass or 191 parallactic angle values is for one subfilter to be fit with very low or 192 negative values, while another subfilter is fit with very high values. This 193 typically appears in the form of holes next to sources in one subfilter, 194 and corresponding extended wings in another. Because each subfilter has 195 a narrow bandwidth we assume that physical sources that are above the noise 196 level will not vary in flux by more than a factor of `frequencyClampFactor` 197 between subfilters, and pixels that have flux deviations larger than that 198 factor will have the excess flux distributed evenly among all subfilters. 201 ConfigClass = DcrAssembleCoaddConfig
202 _DefaultName =
"dcrAssembleCoadd" 205 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
206 """Assemble a coadd from a set of warps. 208 Coadd a set of Warps. Compute weights to be applied to each Warp and 209 find scalings to match the photometric zeropoint to a reference Warp. 210 Assemble the Warps using run method. 211 Forward model chromatic effects across multiple subfilters, 212 and subtract from the input Warps to build sets of residuals. 213 Use the residuals to construct a new ``DcrModel`` for each subfilter, 214 and iterate until the model converges. 215 Interpolate over NaNs and optionally write the coadd to disk. 216 Return the coadded exposure. 220 dataRef : `lsst.daf.persistence.ButlerDataRef` 221 Data reference defining the patch for coaddition and the 223 selectDataList : `list` of `lsst.daf.persistence.ButlerDataRef` 224 List of data references to warps. Data to be coadded will be 225 selected from this list based on overlap with the patch defined by 230 results : `lsst.pipe.base.Struct` 231 The Struct contains the following fields: 233 - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`) 234 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`) 235 - ``dcrCoadds``: `list` of coadded exposures for each subfilter 236 - ``dcrNImages``: `list` of exposure count images for each subfilter 238 if (selectDataList
is None and warpRefList
is None)
or (selectDataList
and warpRefList):
239 raise RuntimeError(
"runDataRef must be supplied either a selectDataList or warpRefList")
241 results = AssembleCoaddTask.runDataRef(self, dataRef, selectDataList=selectDataList,
242 warpRefList=warpRefList)
245 self.log.
warn(
"Could not construct DcrModel for patch %s: no data to coadd.",
246 skyInfo.patchInfo.getIndex())
248 for subfilter
in range(self.config.dcrNumSubfilters):
250 if self.config.doWrite:
251 self.log.
info(
"Persisting dcrCoadd")
252 dataRef.put(results.dcrCoadds[subfilter],
"dcrCoadd", subfilter=subfilter,
253 numSubfilters=self.config.dcrNumSubfilters)
254 if self.config.doNImage
and results.dcrNImages
is not None:
255 dataRef.put(results.dcrNImages[subfilter],
"dcrCoadd_nImage", subfilter=subfilter,
256 numSubfilters=self.config.dcrNumSubfilters)
261 """Prepare the DCR coadd by iterating through the visitInfo of the input warps. 263 Sets the properties ``warpCtrl`` and ``bufferSize``. 267 templateCoadd : `lsst.afw.image.ExposureF` 268 The initial coadd exposure before accounting for DCR. 269 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 270 The data references to the input warped exposures. 271 weightList : `list` of `float` 272 The weight to give each input exposure in the coadd 273 Will be modified in place if ``doAirmassWeight`` is set. 277 dcrModels : `lsst.pipe.tasks.DcrModel` 278 Best fit model of the true sky after correcting chromatic effects. 283 If ``lambdaMin`` is missing from the Mapper class of the obs package being used. 285 filterInfo = templateCoadd.getFilter()
286 if np.isnan(filterInfo.getFilterProperty().getLambdaMin()):
287 raise NotImplementedError(
"No minimum/maximum wavelength information found" 288 " in the filter definition! Please add lambdaMin and lambdaMax" 289 " to the Mapper class in your obs package.")
294 for visitNum, tempExpRef
in enumerate(tempExpRefList):
295 visitInfo = tempExpRef.get(tempExpName +
"_visitInfo")
296 airmass = visitInfo.getBoresightAirmass()
297 parallacticAngle = visitInfo.getBoresightParAngle().asDegrees()
298 airmassList[tempExpRef.dataId[
"visit"]] = airmass
299 angleList[tempExpRef.dataId[
"visit"]] = parallacticAngle
300 if self.config.doAirmassWeight:
301 weightList[visitNum] *= airmass
302 dcrShifts.append(np.max(np.abs(
calculateDcr(visitInfo, templateCoadd.getWcs(),
303 filterInfo, self.config.dcrNumSubfilters))))
304 self.log.
info(
"Selected airmasses:\n%s", airmassList)
305 self.log.
info(
"Selected parallactic angles:\n%s", angleList)
310 warpInterpLength =
max(self.config.subregionSize)
312 self.config.maskWarpMethod,
313 cacheSize=warpCache, interpLength=warpInterpLength)
314 dcrModels = DcrModel.fromImage(templateCoadd.maskedImage,
315 self.config.dcrNumSubfilters,
316 filterInfo=filterInfo,
317 psf=templateCoadd.getPsf())
320 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
321 supplementaryData=None):
322 """Assemble the coadd. 324 Requires additional inputs Struct ``supplementaryData`` to contain a 325 ``templateCoadd`` that serves as the model of the static sky. 327 Find artifacts and apply them to the warps' masks creating a list of 328 alternative masks with a new "CLIPPED" plane and updated "NO_DATA" plane 329 Then pass these alternative masks to the base class's assemble method. 331 Divide the ``templateCoadd`` evenly between each subfilter of a 332 ``DcrModel`` as the starting best estimate of the true wavelength- 333 dependent sky. Forward model the ``DcrModel`` using the known 334 chromatic effects in each subfilter and calculate a convergence metric 335 based on how well the modeled template matches the input warps. If 336 the convergence has not yet reached the desired threshold, then shift 337 and stack the residual images to build a new ``DcrModel``. Apply 338 conditioning to prevent oscillating solutions between iterations or 341 Once the ``DcrModel`` reaches convergence or the maximum number of 342 iterations has been reached, fill the metadata for each subfilter 343 image and make them proper ``coaddExposure``s. 347 skyInfo : `lsst.pipe.base.Struct` 348 Patch geometry information, from getSkyInfo 349 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 350 The data references to the input warped exposures. 351 imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 352 The image scalars correct for the zero point of the exposures. 353 weightList : `list` of `float` 354 The weight to give each input exposure in the coadd 355 supplementaryData : `lsst.pipe.base.Struct` 356 Result struct returned by ``makeSupplementaryData`` with components: 358 - ``templateCoadd``: coadded exposure (`lsst.afw.image.Exposure`) 362 result : `lsst.pipe.base.Struct` 363 Result struct with components: 365 - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`) 366 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`) 367 - ``dcrCoadds``: `list` of coadded exposures for each subfilter 368 - ``dcrNImages``: `list` of exposure count images for each subfilter 370 minNumIter = self.config.minNumIter
or self.config.dcrNumSubfilters
371 maxNumIter = self.config.maxNumIter
or self.config.dcrNumSubfilters*3
372 templateCoadd = supplementaryData.templateCoadd
373 baseMask = templateCoadd.mask.clone()
376 baseVariance = templateCoadd.variance.clone()
377 baseVariance /= self.config.dcrNumSubfilters
378 spanSetMaskList = self.
findArtifacts(templateCoadd, tempExpRefList, imageScalerList)
380 templateCoadd.setMask(baseMask)
381 badMaskPlanes = self.config.badMaskPlanes[:]
386 badPixelMask = templateCoadd.mask.getPlaneBitMask(badMaskPlanes)
389 dcrModels = self.
prepareDcrInputs(templateCoadd, tempExpRefList, weightList)
390 if self.config.doNImage:
392 tempExpRefList, spanSetMaskList, stats.ctrl)
393 nImage = afwImage.ImageU(skyInfo.bbox)
397 for dcrNImage
in dcrNImages:
403 nSubregions = (
ceil(skyInfo.bbox.getHeight()/subregionSize[1]) *
404 ceil(skyInfo.bbox.getWidth()/subregionSize[0]))
406 for subBBox
in self.
_subBBoxIter(skyInfo.bbox, subregionSize):
409 self.log.
info(
"Computing coadd over patch %s subregion %s of %s: %s",
410 skyInfo.patchInfo.getIndex(), subIter, nSubregions, subBBox)
413 dcrBBox.clip(dcrModels.bbox)
416 imageScalerList, weightList, spanSetMaskList,
418 self.log.
info(
"Initial convergence : %s", convergenceMetric)
419 convergenceList = [convergenceMetric]
421 convergenceCheck = 1.
422 refImage = templateCoadd.maskedImage
423 while (convergenceCheck > self.config.convergenceThreshold
or modelIter <= minNumIter):
426 weightList, spanSetMaskList, stats.flags, stats.ctrl,
427 convergenceMetric, baseMask, gain,
428 modelWeights, refImage)
429 if self.config.useConvergence:
431 imageScalerList, weightList,
434 if convergenceMetric == 0:
435 self.log.
warn(
"Coadd patch %s subregion %s had convergence metric of 0.0 which is " 436 "most likely due to there being no valid data in the region.",
437 skyInfo.patchInfo.getIndex(), subIter)
439 convergenceCheck = (convergenceList[-1] - convergenceMetric)/convergenceMetric
440 if (convergenceCheck < 0) & (modelIter > minNumIter):
441 self.log.
warn(
"Coadd patch %s subregion %s diverged before reaching maximum " 442 "iterations or desired convergence improvement of %s." 444 skyInfo.patchInfo.getIndex(), subIter,
445 self.config.convergenceThreshold, convergenceCheck)
447 convergenceList.append(convergenceMetric)
448 if modelIter > maxNumIter:
449 if self.config.useConvergence:
450 self.log.
warn(
"Coadd patch %s subregion %s reached maximum iterations " 451 "before reaching desired convergence improvement of %s." 452 " Final convergence improvement: %s",
453 skyInfo.patchInfo.getIndex(), subIter,
454 self.config.convergenceThreshold, convergenceCheck)
457 if self.config.useConvergence:
458 self.log.
info(
"Iteration %s with convergence metric %s, %.4f%% improvement (gain: %.2f)",
459 modelIter, convergenceMetric, 100.*convergenceCheck, gain)
462 if self.config.useConvergence:
463 self.log.
info(
"Coadd patch %s subregion %s finished with " 464 "convergence metric %s after %s iterations",
465 skyInfo.patchInfo.getIndex(), subIter, convergenceMetric, modelIter)
467 self.log.
info(
"Coadd patch %s subregion %s finished after %s iterations",
468 skyInfo.patchInfo.getIndex(), subIter, modelIter)
469 if self.config.useConvergence
and convergenceMetric > 0:
470 self.log.
info(
"Final convergence improvement was %.4f%% overall",
471 100*(convergenceList[0] - convergenceMetric)/convergenceMetric)
473 dcrCoadds = self.
fillCoadd(dcrModels, skyInfo, tempExpRefList, weightList,
474 calibration=self.scaleZeroPoint.getCalib(),
475 coaddInputs=templateCoadd.getInfo().getCoaddInputs(),
477 variance=baseVariance)
479 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
480 dcrCoadds=dcrCoadds, dcrNImages=dcrNImages)
482 def calculateNImage(self, dcrModels, bbox, tempExpRefList, spanSetMaskList, statsCtrl):
483 """Calculate the number of exposures contributing to each subfilter. 487 dcrModels : `lsst.pipe.tasks.DcrModel` 488 Best fit model of the true sky after correcting chromatic effects. 489 bbox : `lsst.afw.geom.box.Box2I` 490 Bounding box of the patch to coadd. 491 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 492 The data references to the input warped exposures. 493 spanSetMaskList : `list` of `dict` containing spanSet lists, or None 494 Each element is dict with keys = mask plane name to add the spans to 495 statsCtrl : `lsst.afw.math.StatisticsControl` 496 Statistics control object for coadd 500 dcrNImages : `list` of `lsst.afw.image.ImageU` 501 List of exposure count images for each subfilter 503 dcrNImages = [afwImage.ImageU(bbox)
for subfilter
in range(self.config.dcrNumSubfilters)]
505 for tempExpRef, altMaskSpans
in zip(tempExpRefList, spanSetMaskList):
506 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
507 visitInfo = exposure.getInfo().getVisitInfo()
508 wcs = exposure.getInfo().getWcs()
510 if altMaskSpans
is not None:
512 dcrShift =
calculateDcr(visitInfo, wcs, dcrModels.filter, self.config.dcrNumSubfilters)
513 for dcr, dcrNImage
in zip(dcrShift, dcrNImages):
514 shiftedImage =
applyDcr(exposure.maskedImage, dcr, self.
warpCtrl, useInverse=
True)
515 dcrNImage.array[shiftedImage.mask.array & statsCtrl.getAndMask() == 0] += 1
518 def dcrAssembleSubregion(self, dcrModels, bbox, dcrBBox, tempExpRefList, imageScalerList, weightList,
519 spanSetMaskList, statsFlags, statsCtrl, convergenceMetric,
520 baseMask, gain, modelWeights, refImage):
521 """Assemble the DCR coadd for a sub-region. 523 Build a DCR-matched template for each input exposure, then shift the 524 residuals according to the DCR in each subfilter. 525 Stack the shifted residuals and apply them as a correction to the 526 solution from the previous iteration. 527 Restrict the new model solutions from varying by more than a factor of 528 `modelClampFactor` from the last solution, and additionally restrict the 529 individual subfilter models from varying by more than a factor of 530 `frequencyClampFactor` from their average. 531 Finally, mitigate potentially oscillating solutions by averaging the new 532 solution with the solution from the previous iteration, weighted by 533 their convergence metric. 537 dcrModels : `lsst.pipe.tasks.DcrModel` 538 Best fit model of the true sky after correcting chromatic effects. 539 bbox : `lsst.afw.geom.box.Box2I` 540 Bounding box of the subregion to coadd. 541 dcrBBox : `lsst.afw.geom.box.Box2I` 542 Sub-region of the coadd which includes a buffer to allow for DCR. 543 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 544 The data references to the input warped exposures. 545 imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 546 The image scalars correct for the zero point of the exposures. 547 weightList : `list` of `float` 548 The weight to give each input exposure in the coadd 549 spanSetMaskList : `list` of `dict` containing spanSet lists, or None 550 Each element is dict with keys = mask plane name to add the spans to 551 statsFlags : `lsst.afw.math.Property` 552 Statistics settings for coaddition. 553 statsCtrl : `lsst.afw.math.StatisticsControl` 554 Statistics control object for coadd 555 convergenceMetric : `float` 556 Quality of fit metric for the matched templates of the input images. 557 baseMask : `lsst.afw.image.Mask` 558 Mask of the initial template coadd. 559 gain : `float`, optional 560 Relative weight to give the new solution when updating the model. 561 modelWeights : `numpy.ndarray` or `float` 562 A 2D array of weight values that tapers smoothly to zero away from detected sources. 563 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 564 refImage : `lsst.afw.image.MaskedImage` 565 A reference image used to supply the default pixel values. 568 residualGeneratorList = []
570 for tempExpRef, imageScaler, altMaskSpans
in zip(tempExpRefList, imageScalerList, spanSetMaskList):
571 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=dcrBBox)
572 visitInfo = exposure.getInfo().getVisitInfo()
573 wcs = exposure.getInfo().getWcs()
574 maskedImage = exposure.maskedImage
575 templateImage = dcrModels.buildMatchedTemplate(warpCtrl=self.
warpCtrl, visitInfo=visitInfo,
576 bbox=dcrBBox, wcs=wcs, mask=baseMask,
577 splitSubfilters=self.config.splitSubfilters)
578 imageScaler.scaleMaskedImage(maskedImage)
579 if altMaskSpans
is not None:
582 if self.config.removeMaskPlanes:
584 maskedImage -= templateImage
585 residualGeneratorList.append(self.
dcrResiduals(maskedImage, visitInfo, dcrBBox, wcs,
589 statsFlags, statsCtrl, weightList,
590 mask=baseMask, gain=gain,
591 modelWeights=modelWeights,
593 dcrModels.assign(dcrSubModelOut, bbox)
596 """Prepare a residual image for stacking in each subfilter by applying the reverse DCR shifts. 600 residual : `lsst.afw.image.MaskedImageF` 601 The residual masked image for one exposure, 602 after subtracting the matched template 603 visitInfo : `lsst.afw.image.VisitInfo` 604 Metadata for the exposure. 605 bbox : `lsst.afw.geom.box.Box2I` 606 Sub-region of the coadd 607 wcs : `lsst.afw.geom.SkyWcs` 608 Coordinate system definition (wcs) for the exposure. 609 filterInfo : `lsst.afw.image.Filter` 610 The filter definition, set in the current instruments' obs package. 611 Required for any calculation of DCR, including making matched templates. 615 residualImage : `lsst.afw.image.maskedImageF` 616 The residual image for the next subfilter, shifted for DCR. 618 dcrShift =
calculateDcr(visitInfo, wcs, filterInfo, self.config.dcrNumSubfilters)
623 statsFlags, statsCtrl, weightList,
624 mask, gain, modelWeights, refImage):
625 """Calculate a new DcrModel from a set of image residuals. 629 dcrModels : `lsst.pipe.tasks.DcrModel` 630 Current model of the true sky after correcting chromatic effects. 631 residualGeneratorList : `generator` of `lsst.afw.image.maskedImageF` 632 The residual image for the next subfilter, shifted for DCR. 633 bbox : `lsst.afw.geom.box.Box2I` 634 Sub-region of the coadd 635 statsFlags : `lsst.afw.math.Property` 636 Statistics settings for coaddition. 637 statsCtrl : `lsst.afw.math.StatisticsControl` 638 Statistics control object for coadd 639 weightList : `list` of `float` 640 The weight to give each input exposure in the coadd 641 mask : `lsst.afw.image.Mask` 642 Mask to use for each new model image. 644 Relative weight to give the new solution when updating the model. 645 modelWeights : `numpy.ndarray` or `float` 646 A 2D array of weight values that tapers smoothly to zero away from detected sources. 647 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 648 refImage : `lsst.afw.image.MaskedImage` 649 A reference image used to supply the default pixel values. 653 dcrModel : `lsst.pipe.tasks.DcrModel` 654 New model of the true sky after correcting chromatic effects. 657 clipped = dcrModels.mask.getPlaneBitMask(
"CLIPPED")
659 for subfilter, model
in enumerate(dcrModels):
660 residualsList = [next(residualGenerator)
for residualGenerator
in residualGeneratorList]
663 residual.setXY0(bbox.getBegin())
665 residual += model[bbox]
668 badPixels = ~np.isfinite(newModel.image.array)
671 newModel.setMask(mask[bbox])
672 newModel.image.array[badPixels] = model[bbox].image.array[badPixels]
673 if self.config.regularizeModelIterations > 0:
674 dcrModels.regularizeModelIter(subfilter, newModel, bbox,
675 self.config.regularizeModelIterations,
676 self.config.regularizationWidth)
677 newModelImages.append(newModel)
678 if self.config.regularizeModelFrequency > 0:
679 dcrModels.regularizeModelFreq(newModelImages, bbox, statsCtrl,
680 self.config.regularizeModelFrequency,
681 self.config.regularizationWidth,
683 dcrModels.conditionDcrModel(newModelImages, bbox, gain=gain)
685 return DcrModel(newModelImages, dcrModels.filter, dcrModels.psf)
688 weightList, spanSetMaskList, statsCtrl):
689 """Calculate a quality of fit metric for the matched templates. 693 dcrModels : `lsst.pipe.tasks.DcrModel` 694 Best fit model of the true sky after correcting chromatic effects. 695 bbox : `lsst.afw.geom.box.Box2I` 697 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 698 The data references to the input warped exposures. 699 imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 700 The image scalars correct for the zero point of the exposures. 701 weightList : `list` of `float` 702 The weight to give each input exposure in the coadd 703 spanSetMaskList : `list` of `dict` containing spanSet lists, or None 704 Each element is dict with keys = mask plane name to add the spans to 705 statsCtrl : `lsst.afw.math.StatisticsControl` 706 Statistics control object for coadd 710 convergenceMetric : `float` 711 Quality of fit metric for all input exposures, within the sub-region 713 significanceImage = np.abs(dcrModels.getReferenceImage(bbox))
715 significanceImage += nSigma*dcrModels.calculateNoiseCutoff(dcrModels[1], statsCtrl,
717 if np.max(significanceImage) == 0:
718 significanceImage += 1.
723 zipIterables = zip(tempExpRefList, weightList, imageScalerList, spanSetMaskList)
724 for tempExpRef, expWeight, imageScaler, altMaskSpans
in zipIterables:
725 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
726 imageScaler.scaleMaskedImage(exposure.maskedImage)
728 altMaskSpans=altMaskSpans)
729 metric += singleMetric*expWeight
730 metricList[tempExpRef.dataId[
"visit"]] = singleMetric
732 self.log.
info(
"Individual metrics:\n%s", metricList)
733 return 1.0
if weight == 0.0
else metric/weight
736 statsCtrl, altMaskSpans=None):
737 """Calculate a quality of fit metric for a single matched template. 741 dcrModels : `lsst.pipe.tasks.DcrModel` 742 Best fit model of the true sky after correcting chromatic effects. 743 exposure : `lsst.afw.image.ExposureF` 744 The input warped exposure to evaluate. 745 significanceImage : `numpy.ndarray` 746 Array of weights for each pixel corresponding to its significance 747 for the convergence calculation. 748 statsCtrl : `lsst.afw.math.StatisticsControl` 749 Statistics control object for coadd 750 altMaskSpans : `dict` containing spanSet lists, or None 751 The keys of the `dict` equal the mask plane name to add the spans to 755 convergenceMetric : `float` 756 Quality of fit metric for one exposure, within the sub-region. 758 convergeMask = exposure.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
759 templateImage = dcrModels.buildMatchedTemplate(warpCtrl=self.
warpCtrl,
760 visitInfo=exposure.getInfo().getVisitInfo(),
761 bbox=exposure.getBBox(),
762 wcs=exposure.getInfo().getWcs())
763 diffVals = np.abs(exposure.image.array - templateImage.image.array)*significanceImage
764 refVals = np.abs(exposure.image.array + templateImage.image.array)*significanceImage/2.
766 finitePixels = np.isfinite(diffVals)
767 if altMaskSpans
is not None:
769 goodMaskPixels = exposure.mask.array & statsCtrl.getAndMask() == 0
770 convergeMaskPixels = exposure.mask.array & convergeMask > 0
771 usePixels = finitePixels & goodMaskPixels & convergeMaskPixels
772 if np.sum(usePixels) == 0:
775 diffUse = diffVals[usePixels]
776 refUse = refVals[usePixels]
777 metric = np.sum(diffUse/np.median(diffUse))/np.sum(refUse/np.median(diffUse))
781 """Add a list of sub-band coadds together. 785 dcrCoadds : `list` of `lsst.afw.image.ExposureF` 786 A list of coadd exposures, each exposure containing 787 the model for one subfilter. 791 coaddExposure : `lsst.afw.image.ExposureF` 792 A single coadd exposure that is the sum of the sub-bands. 794 coaddExposure = dcrCoadds[0].
clone()
795 for coadd
in dcrCoadds[1:]:
796 coaddExposure.maskedImage += coadd.maskedImage
799 def fillCoadd(self, dcrModels, skyInfo, tempExpRefList, weightList, calibration=None, coaddInputs=None,
800 mask=None, variance=None):
801 """Create a list of coadd exposures from a list of masked images. 805 dcrModels : `lsst.pipe.tasks.DcrModel` 806 Best fit model of the true sky after correcting chromatic effects. 807 skyInfo : `lsst.pipe.base.Struct` 808 Patch geometry information, from getSkyInfo 809 tempExpRefList : `list` of `lsst.daf.persistence.ButlerDataRef` 810 The data references to the input warped exposures. 811 weightList : `list` of `float` 812 The weight to give each input exposure in the coadd 813 calibration : `lsst.afw.Image.Calib`, optional 814 Scale factor to set the photometric zero point of an exposure. 815 coaddInputs : `lsst.afw.Image.CoaddInputs`, optional 816 A record of the observations that are included in the coadd. 817 mask : `lsst.afw.image.Mask`, optional 818 Optional mask to override the values in the final coadd. 819 variance : `lsst.afw.image.Image`, optional 820 Optional variance plane to override the values in the final coadd. 824 dcrCoadds : `list` of `lsst.afw.image.ExposureF` 825 A list of coadd exposures, each exposure containing 826 the model for one subfilter. 829 for model
in dcrModels:
830 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
831 if calibration
is not None:
832 coaddExposure.setCalib(calibration)
833 if coaddInputs
is not None:
834 coaddExposure.getInfo().setCoaddInputs(coaddInputs)
838 coaddExposure.setMaskedImage(model[skyInfo.bbox])
840 coaddExposure.setMask(mask)
841 if variance
is not None:
842 coaddExposure.setVariance(variance)
843 dcrCoadds.append(coaddExposure)
847 """Calculate the gain to use for the current iteration. 849 After calculating a new DcrModel, each value is averaged with the 850 value in the corresponding pixel from the previous iteration. This 851 reduces oscillating solutions that iterative techniques are plagued by, 852 and speeds convergence. By far the biggest changes to the model 853 happen in the first couple iterations, so we can also use a more 854 aggressive gain later when the model is changing slowly. 858 convergenceList : `list` of `float` 859 The quality of fit metric from each previous iteration. 860 gainList : `list` of `float` 861 The gains used in each previous iteration: appended with the new 863 Gains are numbers between ``self.config.baseGain`` and 1. 868 Relative weight to give the new solution when updating the model. 869 A value of 1.0 gives equal weight to both solutions. 874 If ``len(convergenceList) != len(gainList)+1``. 876 nIter = len(convergenceList)
877 if nIter != len(gainList) + 1:
878 raise ValueError(
"convergenceList (%d) must be one element longer than gainList (%d)." 879 % (len(convergenceList), len(gainList)))
881 if self.config.baseGain
is None:
884 baseGain = 1./(self.config.dcrNumSubfilters - 1)
886 baseGain = self.config.baseGain
888 if self.config.useProgressiveGain
and nIter > 2:
896 estFinalConv = [((1 + gainList[i])*convergenceList[i + 1] - convergenceList[i])/gainList[i]
897 for i
in range(nIter - 1)]
900 estFinalConv = np.array(estFinalConv)
901 estFinalConv[estFinalConv < 0] = 0
903 estFinalConv = np.median(estFinalConv[
max(nIter - 5, 0):])
904 lastGain = gainList[-1]
905 lastConv = convergenceList[-2]
906 newConv = convergenceList[-1]
911 predictedConv = (estFinalConv*lastGain + lastConv)/(1. + lastGain)
917 delta = (predictedConv - newConv)/((lastConv - estFinalConv)/(1 + lastGain))
918 newGain = 1 -
abs(delta)
920 newGain = (newGain + lastGain)/2.
921 gain =
max(baseGain, newGain)
924 gainList.append(gain)
928 """Build an array that smoothly tapers to 0 away from detected sources. 932 dcrModels : `lsst.pipe.tasks.DcrModel` 933 Best fit model of the true sky after correcting chromatic effects. 934 dcrBBox : `lsst.afw.geom.box.Box2I` 935 Sub-region of the coadd which includes a buffer to allow for DCR. 939 weights : `numpy.ndarray` or `float` 940 A 2D array of weight values that tapers smoothly to zero away from detected sources. 941 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 946 If ``useModelWeights`` is set and ``modelWeightsWidth`` is negative. 948 if not self.config.useModelWeights:
950 if self.config.modelWeightsWidth < 0:
951 raise ValueError(
"modelWeightsWidth must not be negative if useModelWeights is set")
952 convergeMask = dcrModels.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
953 convergeMaskPixels = dcrModels.mask[dcrBBox].array & convergeMask > 0
954 weights = np.zeros_like(dcrModels[0][dcrBBox].image.array)
955 weights[convergeMaskPixels] = 1.
956 weights = ndimage.filters.gaussian_filter(weights, self.config.modelWeightsWidth)
957 weights /= np.max(weights)
961 """Smoothly replace model pixel values with those from a 962 reference at locations away from detected sources. 966 modelImages : `list` of `lsst.afw.image.MaskedImage` 967 The new DCR model images from the current iteration. 968 The values will be modified in place. 969 refImage : `lsst.afw.image.MaskedImage` 970 A reference image used to supply the default pixel values. 971 modelWeights : `numpy.ndarray` or `float` 972 A 2D array of weight values that tapers smoothly to zero away from detected sources. 973 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 975 if self.config.useModelWeights:
976 for model
in modelImages:
977 model.image.array *= modelWeights
978 model.image.array += refImage.image.array*(1. - modelWeights)/self.config.dcrNumSubfilters
Extent< int, N > ceil(Extent< double, N > const &input) noexcept
Return the component-wise ceil (round towards more positive).
Angle abs(Angle const &a)
def runDataRef(self, dataRef, selectDataList=None, warpRefList=None)
def newModelFromResidual(self, dcrModels, residualGeneratorList, bbox, statsFlags, statsCtrl, weightList, mask, gain, modelWeights, refImage)
def findArtifacts(self, templateCoadd, tempExpRefList, imageScalerList)
def assembleMetadata(self, coaddExposure, tempExpRefList, weightList)
def calculateNImage(self, dcrModels, bbox, tempExpRefList, spanSetMaskList, statsCtrl)
void setCoaddEdgeBits(lsst::afw::image::Mask< lsst::afw::image::MaskPixel > &coaddMask, lsst::afw::image::Image< WeightPixelT > const &weightMap)
set edge bits of coadd mask based on weight map
Parameters to control convolution.
def removeMaskPlanes(self, maskedImage)
def fillCoadd(self, dcrModels, skyInfo, tempExpRefList, weightList, calibration=None, coaddInputs=None, mask=None, variance=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 dcrAssembleSubregion(self, dcrModels, bbox, dcrBBox, tempExpRefList, imageScalerList, weightList, spanSetMaskList, statsFlags, statsCtrl, convergenceMetric, baseMask, gain, modelWeights, refImage)
def getSkyInfo(self, patchRef)
Use getSkyinfo to return the skyMap, tract and patch information, wcs and the outer bbox of the patch...
def getTempExpDatasetName(self, warpType="direct")
def prepareStats(self, mask=None)
def dcrResiduals(self, residual, visitInfo, bbox, wcs, filterInfo)
def applyModelWeights(self, modelImages, refImage, modelWeights)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, supplementaryData=None)
def setRejectedMaskMapping(statsCtrl)
def applyDcr(maskedImage, dcr, warpCtrl, bbox=None, useInverse=False, splitSubfilters=False)
def prepareDcrInputs(self, templateCoadd, tempExpRefList, weightList)
std::shared_ptr< image::MaskedImage< PixelT > > statisticsStack(image::MaskedImage< PixelT > const &image, Property flags, char dimension, StatisticsControl const &sctrl)
A function to compute statistics on the rows or columns of an image.
def processResults(self, coaddExposure, dataRef)
def calculateGain(self, convergenceList, gainList)
def calculateDcr(visitInfo, wcs, filterInfo, dcrNumSubfilters, splitSubfilters=False)
def _subBBoxIter(bbox, subregionSize)
An integer coordinate rectangle.
def stackCoadd(self, dcrCoadds)
def calculateModelWeights(self, dcrModels, dcrBBox)