25from scipy
import ndimage
30from lsst.daf.butler
import DeferredDatasetHandle
36import lsst.utils
as utils
37from lsst.utils.timer
import timeMethod
38from .assembleCoadd
import (AssembleCoaddConnections,
40 CompareWarpAssembleCoaddConfig,
41 CompareWarpAssembleCoaddTask)
42from .coaddBase
import makeSkyInfo
43from .measurePsf
import MeasurePsfTask
45__all__ = [
"DcrAssembleCoaddConnections",
"DcrAssembleCoaddTask",
"DcrAssembleCoaddConfig"]
49 dimensions=(
"tract",
"patch",
"band",
"skymap"),
50 defaultTemplates={
"inputWarpName":
"deep",
51 "inputCoaddName":
"deep",
52 "outputCoaddName":
"dcr",
56 inputWarps = pipeBase.connectionTypes.Input(
57 doc=(
"Input list of warps to be assembled i.e. stacked."
58 "Note that this will often be different than the inputCoaddName."
59 "WarpType (e.g. direct, psfMatched) is controlled by the warpType config parameter"),
60 name=
"{inputWarpName}Coadd_{warpType}Warp",
61 storageClass=
"ExposureF",
62 dimensions=(
"tract",
"patch",
"skymap",
"visit",
"instrument"),
66 templateExposure = pipeBase.connectionTypes.Input(
67 doc=
"Input coadded exposure, produced by previous call to AssembleCoadd",
68 name=
"{fakesType}{inputCoaddName}Coadd{warpTypeSuffix}",
69 storageClass=
"ExposureF",
70 dimensions=(
"tract",
"patch",
"skymap",
"band"),
72 dcrCoadds = pipeBase.connectionTypes.Output(
73 doc=
"Output coadded exposure, produced by stacking input warps",
74 name=
"{fakesType}{outputCoaddName}Coadd{warpTypeSuffix}",
75 storageClass=
"ExposureF",
76 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
79 dcrNImages = pipeBase.connectionTypes.Output(
80 doc=
"Output image of number of input images per pixel",
81 name=
"{outputCoaddName}Coadd_nImage",
82 storageClass=
"ImageU",
83 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
87 def __init__(self, *, config=None):
88 super().__init__(config=config)
89 if not config.doWrite:
90 self.outputs.remove(
"dcrCoadds")
91 if not config.doNImage:
92 self.outputs.remove(
"dcrNImages")
94 self.outputs.remove(
"coaddExposure")
95 self.outputs.remove(
"nImage")
99 pipelineConnections=DcrAssembleCoaddConnections):
100 dcrNumSubfilters = pexConfig.Field(
102 doc=
"Number of sub-filters to forward model chromatic effects to fit the supplied exposures.",
105 maxNumIter = pexConfig.Field(
108 doc=
"Maximum number of iterations of forward modeling.",
111 minNumIter = pexConfig.Field(
114 doc=
"Minimum number of iterations of forward modeling.",
117 convergenceThreshold = pexConfig.Field(
119 doc=
"Target relative change in convergence between iterations of forward modeling.",
122 useConvergence = pexConfig.Field(
124 doc=
"Use convergence test as a forward modeling end condition?"
125 "If not set, skips calculating convergence and runs for ``maxNumIter`` iterations",
128 baseGain = pexConfig.Field(
131 doc=
"Relative weight to give the new solution vs. the last solution when updating the model."
132 "A value of 1.0 gives equal weight to both solutions."
133 "Small values imply slower convergence of the solution, but can "
134 "help prevent overshooting and failures in the fit."
135 "If ``baseGain`` is None, a conservative gain "
136 "will be calculated from the number of subfilters. ",
139 useProgressiveGain = pexConfig.Field(
141 doc=
"Use a gain that slowly increases above ``baseGain`` to accelerate convergence? "
142 "When calculating the next gain, we use up to 5 previous gains and convergence values."
143 "Can be set to False to force the model to change at the rate of ``baseGain``. ",
146 doAirmassWeight = pexConfig.Field(
148 doc=
"Weight exposures by airmass? Useful if there are relatively few high-airmass observations.",
151 modelWeightsWidth = pexConfig.Field(
153 doc=
"Width of the region around detected sources to include in the DcrModel.",
156 useModelWeights = pexConfig.Field(
158 doc=
"Width of the region around detected sources to include in the DcrModel.",
161 splitSubfilters = pexConfig.Field(
163 doc=
"Calculate DCR for two evenly-spaced wavelengths in each subfilter."
164 "Instead of at the midpoint",
167 splitThreshold = pexConfig.Field(
169 doc=
"Minimum DCR difference within a subfilter to use ``splitSubfilters``, in pixels."
170 "Set to 0 to always split the subfilters.",
173 regularizeModelIterations = pexConfig.Field(
175 doc=
"Maximum relative change of the model allowed between iterations."
176 "Set to zero to disable.",
179 regularizeModelFrequency = pexConfig.Field(
181 doc=
"Maximum relative change of the model allowed between subfilters."
182 "Set to zero to disable.",
185 convergenceMaskPlanes = pexConfig.ListField(
187 default=[
"DETECTED"],
188 doc=
"Mask planes to use to calculate convergence."
190 regularizationWidth = pexConfig.Field(
193 doc=
"Minimum radius of a region to include in regularization, in pixels."
195 imageInterpOrder = pexConfig.Field(
197 doc=
"The order of the spline interpolation used to shift the image plane.",
200 accelerateModel = pexConfig.Field(
202 doc=
"Factor to amplify the differences between model planes by to speed convergence.",
205 doCalculatePsf = pexConfig.Field(
207 doc=
"Set to detect stars and recalculate the PSF from the final coadd."
208 "Otherwise the PSF is estimated from a selection of the best input exposures",
211 detectPsfSources = pexConfig.ConfigurableField(
212 target=measAlg.SourceDetectionTask,
213 doc=
"Task to detect sources for PSF measurement, if ``doCalculatePsf`` is set.",
215 measurePsfSources = pexConfig.ConfigurableField(
216 target=SingleFrameMeasurementTask,
217 doc=
"Task to measure sources for PSF measurement, if ``doCalculatePsf`` is set."
219 measurePsf = pexConfig.ConfigurableField(
220 target=MeasurePsfTask,
221 doc=
"Task to measure the PSF of the coadd, if ``doCalculatePsf`` is set.",
223 effectiveWavelength = pexConfig.Field(
224 doc=
"Effective wavelength of the filter, in nm."
225 "Required if transmission curves aren't used."
226 "Support for using transmission curves is to be added in DM-13668.",
229 bandwidth = pexConfig.Field(
230 doc=
"Bandwidth of the physical filter, in nm."
231 "Required if transmission curves aren't used."
232 "Support for using transmission curves is to be added in DM-13668.",
236 def setDefaults(self):
237 CompareWarpAssembleCoaddConfig.setDefaults(self)
238 self.assembleStaticSkyModel.retarget(CompareWarpAssembleCoaddTask)
240 self.assembleStaticSkyModel.warpType = self.warpType
242 self.assembleStaticSkyModel.doNImage =
False
243 self.assembleStaticSkyModel.doWrite =
False
244 self.detectPsfSources.returnOriginalFootprints =
False
245 self.detectPsfSources.thresholdPolarity =
"positive"
247 self.detectPsfSources.thresholdValue = 50
248 self.detectPsfSources.nSigmaToGrow = 2
250 self.detectPsfSources.minPixels = 25
252 self.detectPsfSources.thresholdType =
"pixel_stdev"
255 self.measurePsf.starSelector[
"objectSize"].doFluxLimit =
False
259 """Assemble DCR coadded images from a set of warps.
264 The number of pixels to grow each subregion by to allow for DCR.
268 As
with AssembleCoaddTask, we want to assemble a coadded image
from a set of
269 Warps (also called coadded temporary exposures), including the effects of
270 Differential Chromatic Refraction (DCR).
271 For full details of the mathematics
and algorithm, please see
272 DMTN-037: DCR-matched template generation (https://dmtn-037.lsst.io).
274 This Task produces a DCR-corrected deepCoadd,
as well
as a dcrCoadd
for
275 each subfilter used
in the iterative calculation.
276 It begins by dividing the bandpass-defining filter into N equal bandwidth
277 "subfilters",
and divides the flux
in each pixel
from an initial coadd
278 equally into each
as a
"dcrModel". Because the airmass
and parallactic
279 angle of each individual exposure
is known, we can calculate the shift
280 relative to the center of the band
in each subfilter due to DCR. For each
281 exposure we apply this shift
as a linear transformation to the dcrModels
282 and stack the results to produce a DCR-matched exposure. The matched
283 exposures are subtracted
from the input exposures to produce a set of
284 residual images,
and these residuals are reverse shifted
for each
285 exposures
' subfilters and stacked. The shifted and stacked residuals are
286 added to the dcrModels to produce a new estimate of the flux in each pixel
287 within each subfilter. The dcrModels are solved
for iteratively, which
288 continues until the solution
from a new iteration improves by less than
289 a set percentage,
or a maximum number of iterations
is reached.
290 Two forms of regularization are employed to reduce unphysical results.
291 First, the new solution
is averaged
with the solution
from the previous
292 iteration, which mitigates oscillating solutions where the model
293 overshoots
with alternating very high
and low values.
294 Second, a common degeneracy when the data have a limited range of airmass
or
295 parallactic angle values
is for one subfilter to be fit
with very low
or
296 negative values,
while another subfilter
is fit
with very high values. This
297 typically appears
in the form of holes next to sources
in one subfilter,
298 and corresponding extended wings
in another. Because each subfilter has
299 a narrow bandwidth we assume that physical sources that are above the noise
300 level will
not vary
in flux by more than a factor of `frequencyClampFactor`
301 between subfilters,
and pixels that have flux deviations larger than that
302 factor will have the excess flux distributed evenly among all subfilters.
303 If `splitSubfilters`
is set, then each subfilter will be further sub-
304 divided during the forward modeling step (only). This approximates using
305 a higher number of subfilters that may be necessary
for high airmass
306 observations, but does
not increase the number of free parameters
in the
307 fit. This
is needed when there are high airmass observations which would
308 otherwise have significant DCR even within a subfilter. Because calculating
309 the shifted images takes most of the time, splitting the subfilters
is
310 turned off by way of the `splitThreshold` option
for low-airmass
311 observations that do
not suffer
from DCR within a subfilter.
314 ConfigClass = DcrAssembleCoaddConfig
315 _DefaultName = "dcrAssembleCoadd"
317 def __init__(self, *args, **kwargs):
318 super().__init__(*args, **kwargs)
319 if self.config.doCalculatePsf:
320 self.schema = afwTable.SourceTable.makeMinimalSchema()
321 self.makeSubtask(
"detectPsfSources", schema=self.schema)
322 self.makeSubtask(
"measurePsfSources", schema=self.schema)
323 self.makeSubtask(
"measurePsf", schema=self.schema)
325 @utils.inheritDoc(pipeBase.PipelineTask)
326 def runQuantum(self, butlerQC, inputRefs, outputRefs):
331 Assemble a coadd from a set of Warps.
333 PipelineTask (Gen3) entry point to Coadd a set of Warps.
334 Analogous to `runDataRef`, it prepares all the data products to be
335 passed to `run`,
and processes the results before returning a struct
336 of results to be written out. AssembleCoadd cannot fit all Warps
in memory.
337 Therefore, its inputs are accessed subregion by subregion
338 by the Gen3 `DeferredDatasetHandle` that
is analagous to the Gen2
339 `lsst.daf.persistence.ButlerDataRef`. Any updates to this method should
340 correspond to an update
in `runDataRef`
while both entry points
343 inputData = butlerQC.get(inputRefs)
347 skyMap = inputData[
"skyMap"]
348 outputDataId = butlerQC.quantum.dataId
351 tractId=outputDataId[
'tract'],
352 patchId=outputDataId[
'patch'])
356 warpRefList = inputData[
'inputWarps']
358 inputs = self.prepareInputs(warpRefList)
359 self.log.info(
"Found %d %s", len(inputs.tempExpRefList),
360 self.getTempExpDatasetName(self.warpType))
361 if len(inputs.tempExpRefList) == 0:
362 self.log.warning(
"No coadd temporary exposures found")
365 supplementaryData = self.makeSupplementaryDataGen3(butlerQC, inputRefs, outputRefs)
366 retStruct = self.run(inputData[
'skyInfo'], inputs.tempExpRefList, inputs.imageScalerList,
367 inputs.weightList, supplementaryData=supplementaryData)
369 inputData.setdefault(
'brightObjectMask',
None)
370 for subfilter
in range(self.config.dcrNumSubfilters):
372 retStruct.dcrCoadds[subfilter].setPsf(retStruct.coaddExposure.getPsf())
373 self.processResults(retStruct.dcrCoadds[subfilter], inputData[
'brightObjectMask'], outputDataId)
375 if self.config.doWrite:
376 butlerQC.put(retStruct, outputRefs)
380 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
381 """Assemble a coadd from a set of warps.
383 Coadd a set of Warps. Compute weights to be applied to each Warp and
384 find scalings to match the photometric zeropoint to a reference Warp.
385 Assemble the Warps using run method.
386 Forward model chromatic effects across multiple subfilters,
387 and subtract
from the input Warps to build sets of residuals.
388 Use the residuals to construct a new ``DcrModel``
for each subfilter,
389 and iterate until the model converges.
390 Interpolate over NaNs
and optionally write the coadd to disk.
391 Return the coadded exposure.
395 dataRef : `lsst.daf.persistence.ButlerDataRef`
396 Data reference defining the patch
for coaddition
and the
398 selectDataList : `list` of `lsst.daf.persistence.ButlerDataRef`
399 List of data references to warps. Data to be coadded will be
400 selected
from this list based on overlap
with the patch defined by
405 results : `lsst.pipe.base.Struct`
406 The Struct contains the following fields:
409 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`)
410 - ``dcrCoadds``: `list` of coadded exposures
for each subfilter
411 - ``dcrNImages``: `list` of exposure count images
for each subfilter
413 if (selectDataList
is None and warpRefList
is None)
or (selectDataList
and warpRefList):
414 raise RuntimeError(
"runDataRef must be supplied either a selectDataList or warpRefList")
416 skyInfo = self.getSkyInfo(dataRef)
417 if warpRefList
is None:
418 calExpRefList = self.selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
419 if len(calExpRefList) == 0:
420 self.log.warning(
"No exposures to coadd")
422 self.log.info(
"Coadding %d exposures", len(calExpRefList))
424 warpRefList = self.getTempExpRefList(dataRef, calExpRefList)
426 inputData = self.prepareInputs(warpRefList)
427 self.log.info(
"Found %d %s", len(inputData.tempExpRefList),
428 self.getTempExpDatasetName(self.warpType))
429 if len(inputData.tempExpRefList) == 0:
430 self.log.warning(
"No coadd temporary exposures found")
433 supplementaryData = self.makeSupplementaryData(dataRef, warpRefList=inputData.tempExpRefList)
435 results = self.run(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
436 inputData.weightList, supplementaryData=supplementaryData)
438 self.log.warning(
"Could not construct DcrModel for patch %s: no data to coadd.",
439 skyInfo.patchInfo.getIndex())
442 if self.config.doCalculatePsf:
443 self.measureCoaddPsf(results.coaddExposure)
444 brightObjects = self.readBrightObjectMasks(dataRef)
if self.config.doMaskBrightObjects
else None
445 for subfilter
in range(self.config.dcrNumSubfilters):
447 results.dcrCoadds[subfilter].setPsf(results.coaddExposure.getPsf())
448 self.processResults(results.dcrCoadds[subfilter],
449 brightObjectMasks=brightObjects, dataId=dataRef.dataId)
450 if self.config.doWrite:
451 self.log.info(
"Persisting dcrCoadd")
452 dataRef.put(results.dcrCoadds[subfilter],
"dcrCoadd", subfilter=subfilter,
453 numSubfilters=self.config.dcrNumSubfilters)
454 if self.config.doNImage
and results.dcrNImages
is not None:
455 dataRef.put(results.dcrNImages[subfilter],
"dcrCoadd_nImage", subfilter=subfilter,
456 numSubfilters=self.config.dcrNumSubfilters)
460 @utils.inheritDoc(AssembleCoaddTask)
462 """Load the previously-generated template coadd.
464 This can be removed entirely once we no longer support the Gen 2 butler.
468 templateCoadd : `lsst.pipe.base.Struct`
469 Result struct with components:
471 - ``templateCoadd``: coadded exposure (`lsst.afw.image.ExposureF`)
473 templateCoadd = butlerQC.get(inputRefs.templateExposure)
475 return pipeBase.Struct(templateCoadd=templateCoadd)
477 def measureCoaddPsf(self, coaddExposure):
478 """Detect sources on the coadd exposure and measure the final PSF.
483 The final coadded exposure.
485 table = afwTable.SourceTable.make(self.schema)
486 detResults = self.detectPsfSources.run(table, coaddExposure, clearMask=False)
487 coaddSources = detResults.sources
488 self.measurePsfSources.run(
489 measCat=coaddSources,
490 exposure=coaddExposure
497 psfResults = self.measurePsf.run(coaddExposure, coaddSources)
498 except Exception
as e:
499 self.log.warning(
"Unable to calculate PSF, using default coadd PSF: %s", e)
501 coaddExposure.setPsf(psfResults.psf)
503 def prepareDcrInputs(self, templateCoadd, warpRefList, weightList):
504 """Prepare the DCR coadd by iterating through the visitInfo of the input warps.
506 Sets the property ``bufferSize``.
510 templateCoadd : `lsst.afw.image.ExposureF`
511 The initial coadd exposure before accounting for DCR.
512 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
513 `lsst.daf.persistence.ButlerDataRef`
514 The data references to the input warped exposures.
515 weightList : `list` of `float`
516 The weight to give each input exposure
in the coadd
517 Will be modified
in place
if ``doAirmassWeight``
is set.
521 dcrModels : `lsst.pipe.tasks.DcrModel`
522 Best fit model of the true sky after correcting chromatic effects.
527 If ``lambdaMin``
is missing
from the Mapper
class of the obs package being used.
529 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
530 filterLabel = templateCoadd.getFilterLabel()
531 tempExpName = self.getTempExpDatasetName(self.warpType)
536 for visitNum, warpExpRef
in enumerate(warpRefList):
537 if isinstance(warpExpRef, DeferredDatasetHandle):
539 visitInfo = warpExpRef.get(component=
"visitInfo")
540 psf = warpExpRef.get(component=
"psf")
543 visitInfo = warpExpRef.get(tempExpName +
"_visitInfo")
544 psf = warpExpRef.get(tempExpName).getPsf()
545 visit = warpExpRef.dataId[
"visit"]
547 psfAvgPos = psf.getAveragePosition()
548 psfSize = psf.computeShape(psfAvgPos).getDeterminantRadius()*sigma2fwhm
549 airmass = visitInfo.getBoresightAirmass()
550 parallacticAngle = visitInfo.getBoresightParAngle().asDegrees()
551 airmassDict[visit] = airmass
552 angleDict[visit] = parallacticAngle
553 psfSizeDict[visit] = psfSize
554 if self.config.doAirmassWeight:
555 weightList[visitNum] *= airmass
556 dcrShifts.append(np.max(np.abs(calculateDcr(visitInfo, templateCoadd.getWcs(),
557 self.config.effectiveWavelength,
558 self.config.bandwidth,
559 self.config.dcrNumSubfilters))))
560 self.log.info(
"Selected airmasses:\n%s", airmassDict)
561 self.log.info(
"Selected parallactic angles:\n%s", angleDict)
562 self.log.info(
"Selected PSF sizes:\n%s", psfSizeDict)
563 self.bufferSize = int(np.ceil(np.max(dcrShifts)) + 1)
565 psf = self.selectCoaddPsf(templateCoadd, warpRefList)
566 except Exception
as e:
567 self.log.warning(
"Unable to calculate restricted PSF, using default coadd PSF: %s", e)
569 psf = templateCoadd.getPsf()
570 dcrModels = DcrModel.fromImage(templateCoadd.maskedImage,
571 self.config.dcrNumSubfilters,
572 effectiveWavelength=self.config.effectiveWavelength,
573 bandwidth=self.config.bandwidth,
574 wcs=templateCoadd.getWcs(),
575 filterLabel=filterLabel,
580 def run(self, skyInfo, warpRefList, imageScalerList, weightList,
581 supplementaryData=None):
582 """Assemble the coadd.
584 Requires additional inputs Struct ``supplementaryData`` to contain a
585 ``templateCoadd`` that serves as the model of the static sky.
587 Find artifacts
and apply them to the warps
' masks creating a list of
588 alternative masks with a new
"CLIPPED" plane
and updated
"NO_DATA" plane
589 Then
pass these alternative masks to the base
class's assemble method.
591 Divide the ``templateCoadd`` evenly between each subfilter of a
592 ``DcrModel`` as the starting best estimate of the true wavelength-
593 dependent sky. Forward model the ``DcrModel`` using the known
594 chromatic effects
in each subfilter
and calculate a convergence metric
595 based on how well the modeled template matches the input warps. If
596 the convergence has
not yet reached the desired threshold, then shift
597 and stack the residual images to build a new ``DcrModel``. Apply
598 conditioning to prevent oscillating solutions between iterations
or
601 Once the ``DcrModel`` reaches convergence
or the maximum number of
602 iterations has been reached, fill the metadata
for each subfilter
603 image
and make them proper ``coaddExposure``s.
607 skyInfo : `lsst.pipe.base.Struct`
608 Patch geometry information,
from getSkyInfo
609 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
610 `lsst.daf.persistence.ButlerDataRef`
611 The data references to the input warped exposures.
612 imageScalerList : `list` of `lsst.pipe.task.ImageScaler`
613 The image scalars correct
for the zero point of the exposures.
614 weightList : `list` of `float`
615 The weight to give each input exposure
in the coadd
616 supplementaryData : `lsst.pipe.base.Struct`
617 Result struct returned by ``makeSupplementaryData``
with components:
623 result : `lsst.pipe.base.Struct`
624 Result struct
with components:
627 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`)
628 - ``dcrCoadds``: `list` of coadded exposures
for each subfilter
629 - ``dcrNImages``: `list` of exposure count images
for each subfilter
631 minNumIter = self.config.minNumIter or self.config.dcrNumSubfilters
632 maxNumIter = self.config.maxNumIter
or self.config.dcrNumSubfilters*3
633 templateCoadd = supplementaryData.templateCoadd
634 baseMask = templateCoadd.mask.clone()
637 baseVariance = templateCoadd.variance.clone()
638 baseVariance /= self.config.dcrNumSubfilters
639 spanSetMaskList = self.findArtifacts(templateCoadd, warpRefList, imageScalerList)
641 templateCoadd.setMask(baseMask)
642 badMaskPlanes = self.config.badMaskPlanes[:]
647 badPixelMask = templateCoadd.mask.getPlaneBitMask(badMaskPlanes)
649 stats = self.prepareStats(mask=badPixelMask)
650 dcrModels = self.prepareDcrInputs(templateCoadd, warpRefList, weightList)
651 if self.config.doNImage:
652 dcrNImages, dcrWeights = self.calculateNImage(dcrModels, skyInfo.bbox, warpRefList,
653 spanSetMaskList, stats.ctrl)
654 nImage = afwImage.ImageU(skyInfo.bbox)
658 for dcrNImage
in dcrNImages:
664 nSubregions = (ceil(skyInfo.bbox.getHeight()/subregionSize[1])
665 * ceil(skyInfo.bbox.getWidth()/subregionSize[0]))
667 for subBBox
in self._subBBoxIter(skyInfo.bbox, subregionSize):
670 self.log.info(
"Computing coadd over patch %s subregion %s of %s: %s",
671 skyInfo.patchInfo.getIndex(), subIter, nSubregions, subBBox)
673 dcrBBox.grow(self.bufferSize)
674 dcrBBox.clip(dcrModels.bbox)
675 modelWeights = self.calculateModelWeights(dcrModels, dcrBBox)
676 subExposures = self.loadSubExposures(dcrBBox, stats.ctrl, warpRefList,
677 imageScalerList, spanSetMaskList)
678 convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
679 warpRefList, weightList, stats.ctrl)
680 self.log.info(
"Initial convergence : %s", convergenceMetric)
681 convergenceList = [convergenceMetric]
683 convergenceCheck = 1.
684 refImage = templateCoadd.image
685 while (convergenceCheck > self.config.convergenceThreshold
or modelIter <= minNumIter):
686 gain = self.calculateGain(convergenceList, gainList)
687 self.dcrAssembleSubregion(dcrModels, subExposures, subBBox, dcrBBox, warpRefList,
688 stats.ctrl, convergenceMetric, gain,
689 modelWeights, refImage, dcrWeights)
690 if self.config.useConvergence:
691 convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
692 warpRefList, weightList, stats.ctrl)
693 if convergenceMetric == 0:
694 self.log.warning(
"Coadd patch %s subregion %s had convergence metric of 0.0 which is "
695 "most likely due to there being no valid data in the region.",
696 skyInfo.patchInfo.getIndex(), subIter)
698 convergenceCheck = (convergenceList[-1] - convergenceMetric)/convergenceMetric
699 if (convergenceCheck < 0) & (modelIter > minNumIter):
700 self.log.warning(
"Coadd patch %s subregion %s diverged before reaching maximum "
701 "iterations or desired convergence improvement of %s."
703 skyInfo.patchInfo.getIndex(), subIter,
704 self.config.convergenceThreshold, convergenceCheck)
706 convergenceList.append(convergenceMetric)
707 if modelIter > maxNumIter:
708 if self.config.useConvergence:
709 self.log.warning(
"Coadd patch %s subregion %s reached maximum iterations "
710 "before reaching desired convergence improvement of %s."
711 " Final convergence improvement: %s",
712 skyInfo.patchInfo.getIndex(), subIter,
713 self.config.convergenceThreshold, convergenceCheck)
716 if self.config.useConvergence:
717 self.log.info(
"Iteration %s with convergence metric %s, %.4f%% improvement (gain: %.2f)",
718 modelIter, convergenceMetric, 100.*convergenceCheck, gain)
721 if self.config.useConvergence:
722 self.log.info(
"Coadd patch %s subregion %s finished with "
723 "convergence metric %s after %s iterations",
724 skyInfo.patchInfo.getIndex(), subIter, convergenceMetric, modelIter)
726 self.log.info(
"Coadd patch %s subregion %s finished after %s iterations",
727 skyInfo.patchInfo.getIndex(), subIter, modelIter)
728 if self.config.useConvergence
and convergenceMetric > 0:
729 self.log.info(
"Final convergence improvement was %.4f%% overall",
730 100*(convergenceList[0] - convergenceMetric)/convergenceMetric)
732 dcrCoadds = self.fillCoadd(dcrModels, skyInfo, warpRefList, weightList,
733 calibration=self.scaleZeroPoint.getPhotoCalib(),
734 coaddInputs=templateCoadd.getInfo().getCoaddInputs(),
736 variance=baseVariance)
737 coaddExposure = self.stackCoadd(dcrCoadds)
738 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
739 dcrCoadds=dcrCoadds, dcrNImages=dcrNImages)
741 def calculateNImage(self, dcrModels, bbox, warpRefList, spanSetMaskList, statsCtrl):
742 """Calculate the number of exposures contributing to each subfilter.
746 dcrModels : `lsst.pipe.tasks.DcrModel`
747 Best fit model of the true sky after correcting chromatic effects.
748 bbox : `lsst.geom.box.Box2I`
749 Bounding box of the patch to coadd.
750 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
751 `lsst.daf.persistence.ButlerDataRef`
752 The data references to the input warped exposures.
753 spanSetMaskList : `list` of `dict` containing spanSet lists,
or None
754 Each element of the `dict` contains the new mask plane name
755 (e.g.
"CLIPPED and/or "NO_DATA
") as the key,
756 and the list of SpanSets to apply to the mask.
758 Statistics control object
for coadd
762 dcrNImages : `list` of `lsst.afw.image.ImageU`
763 List of exposure count images
for each subfilter
764 dcrWeights : `list` of `lsst.afw.image.ImageF`
765 Per-pixel weights
for each subfilter.
766 Equal to 1/(number of unmasked images contributing to each pixel).
768 dcrNImages = [afwImage.ImageU(bbox) for subfilter
in range(self.config.dcrNumSubfilters)]
769 dcrWeights = [afwImage.ImageF(bbox)
for subfilter
in range(self.config.dcrNumSubfilters)]
770 tempExpName = self.getTempExpDatasetName(self.warpType)
771 for warpExpRef, altMaskSpans
in zip(warpRefList, spanSetMaskList):
772 if isinstance(warpExpRef, DeferredDatasetHandle):
774 exposure = warpExpRef.get(parameters={
'bbox': bbox})
777 exposure = warpExpRef.get(tempExpName +
"_sub", bbox=bbox)
778 visitInfo = exposure.getInfo().getVisitInfo()
779 wcs = exposure.getInfo().getWcs()
781 if altMaskSpans
is not None:
782 self.applyAltMaskPlanes(mask, altMaskSpans)
783 weightImage = np.zeros_like(exposure.image.array)
784 weightImage[(mask.array & statsCtrl.getAndMask()) == 0] = 1.
787 weightsGenerator = self.dcrResiduals(weightImage, visitInfo, wcs,
788 dcrModels.effectiveWavelength, dcrModels.bandwidth)
789 for shiftedWeights, dcrNImage, dcrWeight
in zip(weightsGenerator, dcrNImages, dcrWeights):
790 dcrNImage.array += np.rint(shiftedWeights).astype(dcrNImage.array.dtype)
791 dcrWeight.array += shiftedWeights
793 weightsThreshold = 1.
794 goodPix = dcrWeights[0].array > weightsThreshold
795 for weights
in dcrWeights[1:]:
796 goodPix = (weights.array > weightsThreshold) & goodPix
797 for subfilter
in range(self.config.dcrNumSubfilters):
798 dcrWeights[subfilter].array[goodPix] = 1./dcrWeights[subfilter].array[goodPix]
799 dcrWeights[subfilter].array[~goodPix] = 0.
800 dcrNImages[subfilter].array[~goodPix] = 0
801 return (dcrNImages, dcrWeights)
804 statsCtrl, convergenceMetric,
805 gain, modelWeights, refImage, dcrWeights):
806 """Assemble the DCR coadd for a sub-region.
808 Build a DCR-matched template for each input exposure, then shift the
809 residuals according to the DCR
in each subfilter.
810 Stack the shifted residuals
and apply them
as a correction to the
811 solution
from the previous iteration.
812 Restrict the new model solutions
from varying by more than a factor of
813 `modelClampFactor`
from the last solution,
and additionally restrict the
814 individual subfilter models
from varying by more than a factor of
815 `frequencyClampFactor`
from their average.
816 Finally, mitigate potentially oscillating solutions by averaging the new
817 solution
with the solution
from the previous iteration, weighted by
818 their convergence metric.
822 dcrModels : `lsst.pipe.tasks.DcrModel`
823 Best fit model of the true sky after correcting chromatic effects.
824 subExposures : `dict` of `lsst.afw.image.ExposureF`
825 The pre-loaded exposures
for the current subregion.
826 bbox : `lsst.geom.box.Box2I`
827 Bounding box of the subregion to coadd.
828 dcrBBox : `lsst.geom.box.Box2I`
829 Sub-region of the coadd which includes a buffer to allow
for DCR.
830 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
831 `lsst.daf.persistence.ButlerDataRef`
832 The data references to the input warped exposures.
834 Statistics control object
for coadd
835 convergenceMetric : `float`
836 Quality of fit metric
for the matched templates of the input images.
837 gain : `float`, optional
838 Relative weight to give the new solution when updating the model.
839 modelWeights : `numpy.ndarray`
or `float`
840 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
841 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
843 A reference image used to supply the default pixel values.
845 Per-pixel weights
for each subfilter.
846 Equal to 1/(number of unmasked images contributing to each pixel).
848 residualGeneratorList = []
850 for warpExpRef
in warpRefList:
851 visit = warpExpRef.dataId[
"visit"]
852 exposure = subExposures[visit]
853 visitInfo = exposure.getInfo().getVisitInfo()
854 wcs = exposure.getInfo().getWcs()
855 templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
856 bbox=exposure.getBBox(),
857 order=self.config.imageInterpOrder,
858 splitSubfilters=self.config.splitSubfilters,
859 splitThreshold=self.config.splitThreshold,
860 amplifyModel=self.config.accelerateModel)
861 residual = exposure.image.array - templateImage.array
863 residual *= exposure.variance.array
867 residualGeneratorList.append(self.dcrResiduals(residual, visitInfo, wcs,
868 dcrModels.effectiveWavelength,
869 dcrModels.bandwidth))
871 dcrSubModelOut = self.newModelFromResidual(dcrModels, residualGeneratorList, dcrBBox, statsCtrl,
873 modelWeights=modelWeights,
875 dcrWeights=dcrWeights)
876 dcrModels.assign(dcrSubModelOut, bbox)
878 def dcrResiduals(self, residual, visitInfo, wcs, effectiveWavelength, bandwidth):
879 """Prepare a residual image for stacking in each subfilter by applying the reverse DCR shifts.
883 residual : `numpy.ndarray`
884 The residual masked image for one exposure,
885 after subtracting the matched template
887 Metadata
for the exposure.
889 Coordinate system definition (wcs)
for the exposure.
893 residualImage : `numpy.ndarray`
894 The residual image
for the next subfilter, shifted
for DCR.
896 if self.config.imageInterpOrder > 1:
899 filteredResidual = ndimage.spline_filter(residual, order=self.config.imageInterpOrder)
902 filteredResidual = residual
905 dcrShift = calculateDcr(visitInfo, wcs, effectiveWavelength, bandwidth, self.config.dcrNumSubfilters,
906 splitSubfilters=
False)
908 yield applyDcr(filteredResidual, dcr, useInverse=
True, splitSubfilters=
False,
909 doPrefilter=
False, order=self.config.imageInterpOrder)
912 gain, modelWeights, refImage, dcrWeights):
913 """Calculate a new DcrModel from a set of image residuals.
917 dcrModels : `lsst.pipe.tasks.DcrModel`
918 Current model of the true sky after correcting chromatic effects.
919 residualGeneratorList : `generator` of `numpy.ndarray`
920 The residual image for the next subfilter, shifted
for DCR.
921 dcrBBox : `lsst.geom.box.Box2I`
922 Sub-region of the coadd which includes a buffer to allow
for DCR.
924 Statistics control object
for coadd
926 Relative weight to give the new solution when updating the model.
927 modelWeights : `numpy.ndarray`
or `float`
928 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
929 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
931 A reference image used to supply the default pixel values.
933 Per-pixel weights
for each subfilter.
934 Equal to 1/(number of unmasked images contributing to each pixel).
938 dcrModel : `lsst.pipe.tasks.DcrModel`
939 New model of the true sky after correcting chromatic effects.
942 for subfilter, model
in enumerate(dcrModels):
943 residualsList = [next(residualGenerator)
for residualGenerator
in residualGeneratorList]
944 residual = np.sum(residualsList, axis=0)
945 residual *= dcrWeights[subfilter][dcrBBox].array
947 newModel = model[dcrBBox].clone()
948 newModel.array += residual
950 badPixels = ~np.isfinite(newModel.array)
951 newModel.array[badPixels] = model[dcrBBox].array[badPixels]
952 if self.config.regularizeModelIterations > 0:
953 dcrModels.regularizeModelIter(subfilter, newModel, dcrBBox,
954 self.config.regularizeModelIterations,
955 self.config.regularizationWidth)
956 newModelImages.append(newModel)
957 if self.config.regularizeModelFrequency > 0:
958 dcrModels.regularizeModelFreq(newModelImages, dcrBBox, statsCtrl,
959 self.config.regularizeModelFrequency,
960 self.config.regularizationWidth)
961 dcrModels.conditionDcrModel(newModelImages, dcrBBox, gain=gain)
962 self.applyModelWeights(newModelImages, refImage[dcrBBox], modelWeights)
963 return DcrModel(newModelImages, dcrModels.filter, dcrModels.effectiveWavelength,
964 dcrModels.bandwidth, dcrModels.psf,
965 dcrModels.mask, dcrModels.variance)
968 """Calculate a quality of fit metric for the matched templates.
972 dcrModels : `lsst.pipe.tasks.DcrModel`
973 Best fit model of the true sky after correcting chromatic effects.
974 subExposures : `dict` of `lsst.afw.image.ExposureF`
975 The pre-loaded exposures for the current subregion.
976 bbox : `lsst.geom.box.Box2I`
978 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
979 `lsst.daf.persistence.ButlerDataRef`
980 The data references to the input warped exposures.
981 weightList : `list` of `float`
982 The weight to give each input exposure
in the coadd
984 Statistics control object
for coadd
988 convergenceMetric : `float`
989 Quality of fit metric
for all input exposures, within the sub-region
991 significanceImage = np.abs(dcrModels.getReferenceImage(bbox))
993 significanceImage += nSigma*dcrModels.calculateNoiseCutoff(dcrModels[1], statsCtrl,
994 bufferSize=self.bufferSize)
995 if np.max(significanceImage) == 0:
996 significanceImage += 1.
1000 for warpExpRef, expWeight
in zip(warpRefList, weightList):
1001 visit = warpExpRef.dataId[
"visit"]
1002 exposure = subExposures[visit][bbox]
1003 singleMetric = self.calculateSingleConvergence(dcrModels, exposure, significanceImage, statsCtrl)
1004 metric += singleMetric
1005 metricList[visit] = singleMetric
1007 self.log.info(
"Individual metrics:\n%s", metricList)
1008 return 1.0
if weight == 0.0
else metric/weight
1011 """Calculate a quality of fit metric for a single matched template.
1015 dcrModels : `lsst.pipe.tasks.DcrModel`
1016 Best fit model of the true sky after correcting chromatic effects.
1017 exposure : `lsst.afw.image.ExposureF`
1018 The input warped exposure to evaluate.
1019 significanceImage : `numpy.ndarray`
1020 Array of weights for each pixel corresponding to its significance
1021 for the convergence calculation.
1023 Statistics control object
for coadd
1027 convergenceMetric : `float`
1028 Quality of fit metric
for one exposure, within the sub-region.
1030 convergeMask = exposure.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
1031 templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
1032 bbox=exposure.getBBox(),
1033 order=self.config.imageInterpOrder,
1034 splitSubfilters=self.config.splitSubfilters,
1035 splitThreshold=self.config.splitThreshold,
1036 amplifyModel=self.config.accelerateModel)
1037 diffVals = np.abs(exposure.image.array - templateImage.array)*significanceImage
1038 refVals = np.abs(exposure.image.array + templateImage.array)*significanceImage/2.
1040 finitePixels = np.isfinite(diffVals)
1041 goodMaskPixels = (exposure.mask.array & statsCtrl.getAndMask()) == 0
1042 convergeMaskPixels = exposure.mask.array & convergeMask > 0
1043 usePixels = finitePixels & goodMaskPixels & convergeMaskPixels
1044 if np.sum(usePixels) == 0:
1047 diffUse = diffVals[usePixels]
1048 refUse = refVals[usePixels]
1049 metric = np.sum(diffUse/np.median(diffUse))/np.sum(refUse/np.median(diffUse))
1053 """Add a list of sub-band coadds together.
1057 dcrCoadds : `list` of `lsst.afw.image.ExposureF`
1058 A list of coadd exposures, each exposure containing
1059 the model for one subfilter.
1063 coaddExposure : `lsst.afw.image.ExposureF`
1064 A single coadd exposure that
is the sum of the sub-bands.
1066 coaddExposure = dcrCoadds[0].clone()
1067 for coadd
in dcrCoadds[1:]:
1068 coaddExposure.maskedImage += coadd.maskedImage
1069 return coaddExposure
1071 def fillCoadd(self, dcrModels, skyInfo, warpRefList, weightList, calibration=None, coaddInputs=None,
1072 mask=None, variance=None):
1073 """Create a list of coadd exposures from a list of masked images.
1077 dcrModels : `lsst.pipe.tasks.DcrModel`
1078 Best fit model of the true sky after correcting chromatic effects.
1079 skyInfo : `lsst.pipe.base.Struct`
1080 Patch geometry information, from getSkyInfo
1081 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
1082 `lsst.daf.persistence.ButlerDataRef`
1083 The data references to the input warped exposures.
1084 weightList : `list` of `float`
1085 The weight to give each input exposure
in the coadd
1086 calibration : `lsst.afw.Image.PhotoCalib`, optional
1087 Scale factor to set the photometric calibration of an exposure.
1088 coaddInputs : `lsst.afw.Image.CoaddInputs`, optional
1089 A record of the observations that are included
in the coadd.
1091 Optional mask to override the values
in the final coadd.
1093 Optional variance plane to override the values
in the final coadd.
1097 dcrCoadds : `list` of `lsst.afw.image.ExposureF`
1098 A list of coadd exposures, each exposure containing
1099 the model
for one subfilter.
1102 refModel = dcrModels.getReferenceImage()
1103 for model
in dcrModels:
1104 if self.config.accelerateModel > 1:
1105 model.array = (model.array - refModel)*self.config.accelerateModel + refModel
1106 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
1107 if calibration
is not None:
1108 coaddExposure.setPhotoCalib(calibration)
1109 if coaddInputs
is not None:
1110 coaddExposure.getInfo().setCoaddInputs(coaddInputs)
1112 self.assembleMetadata(coaddExposure, warpRefList, weightList)
1114 coaddExposure.setPsf(dcrModels.psf)
1115 coaddUtils.setCoaddEdgeBits(dcrModels.mask[skyInfo.bbox], dcrModels.variance[skyInfo.bbox])
1116 maskedImage = afwImage.MaskedImageF(dcrModels.bbox)
1117 maskedImage.image = model
1118 maskedImage.mask = dcrModels.mask
1119 maskedImage.variance = dcrModels.variance
1120 coaddExposure.setMaskedImage(maskedImage[skyInfo.bbox])
1121 coaddExposure.setPhotoCalib(self.scaleZeroPoint.getPhotoCalib())
1122 if mask
is not None:
1123 coaddExposure.setMask(mask)
1124 if variance
is not None:
1125 coaddExposure.setVariance(variance)
1126 dcrCoadds.append(coaddExposure)
1130 """Calculate the gain to use for the current iteration.
1132 After calculating a new DcrModel, each value is averaged
with the
1133 value
in the corresponding pixel
from the previous iteration. This
1134 reduces oscillating solutions that iterative techniques are plagued by,
1135 and speeds convergence. By far the biggest changes to the model
1136 happen
in the first couple iterations, so we can also use a more
1137 aggressive gain later when the model
is changing slowly.
1141 convergenceList : `list` of `float`
1142 The quality of fit metric
from each previous iteration.
1143 gainList : `list` of `float`
1144 The gains used
in each previous iteration: appended
with the new
1146 Gains are numbers between ``self.config.baseGain``
and 1.
1151 Relative weight to give the new solution when updating the model.
1152 A value of 1.0 gives equal weight to both solutions.
1157 If ``len(convergenceList) != len(gainList)+1``.
1159 nIter = len(convergenceList)
1160 if nIter != len(gainList) + 1:
1161 raise ValueError(
"convergenceList (%d) must be one element longer than gainList (%d)."
1162 % (len(convergenceList), len(gainList)))
1164 if self.config.baseGain
is None:
1167 baseGain = 1./(self.config.dcrNumSubfilters - 1)
1169 baseGain = self.config.baseGain
1171 if self.config.useProgressiveGain
and nIter > 2:
1179 estFinalConv = [((1 + gainList[i])*convergenceList[i + 1] - convergenceList[i])/gainList[i]
1180 for i
in range(nIter - 1)]
1183 estFinalConv = np.array(estFinalConv)
1184 estFinalConv[estFinalConv < 0] = 0
1186 estFinalConv = np.median(estFinalConv[max(nIter - 5, 0):])
1187 lastGain = gainList[-1]
1188 lastConv = convergenceList[-2]
1189 newConv = convergenceList[-1]
1194 predictedConv = (estFinalConv*lastGain + lastConv)/(1. + lastGain)
1200 delta = (predictedConv - newConv)/((lastConv - estFinalConv)/(1 + lastGain))
1201 newGain = 1 - abs(delta)
1203 newGain = (newGain + lastGain)/2.
1204 gain = max(baseGain, newGain)
1207 gainList.append(gain)
1211 """Build an array that smoothly tapers to 0 away from detected sources.
1215 dcrModels : `lsst.pipe.tasks.DcrModel`
1216 Best fit model of the true sky after correcting chromatic effects.
1217 dcrBBox : `lsst.geom.box.Box2I`
1218 Sub-region of the coadd which includes a buffer to allow for DCR.
1222 weights : `numpy.ndarray`
or `float`
1223 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
1224 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
1229 If ``useModelWeights``
is set
and ``modelWeightsWidth``
is negative.
1231 if not self.config.useModelWeights:
1233 if self.config.modelWeightsWidth < 0:
1234 raise ValueError(
"modelWeightsWidth must not be negative if useModelWeights is set")
1235 convergeMask = dcrModels.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
1236 convergeMaskPixels = dcrModels.mask[dcrBBox].array & convergeMask > 0
1237 weights = np.zeros_like(dcrModels[0][dcrBBox].array)
1238 weights[convergeMaskPixels] = 1.
1239 weights = ndimage.gaussian_filter(weights, self.config.modelWeightsWidth)
1240 weights /= np.max(weights)
1244 """Smoothly replace model pixel values with those from a
1245 reference at locations away from detected sources.
1250 The new DCR model images
from the current iteration.
1251 The values will be modified
in place.
1253 A reference image used to supply the default pixel values.
1254 modelWeights : `numpy.ndarray`
or `float`
1255 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
1256 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
1258 if self.config.useModelWeights:
1259 for model
in modelImages:
1260 model.array *= modelWeights
1261 model.array += refImage.array*(1. - modelWeights)/self.config.dcrNumSubfilters
1264 """Pre-load sub-regions of a list of exposures.
1268 bbox : `lsst.geom.box.Box2I`
1271 Statistics control object for coadd
1272 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
1273 `lsst.daf.persistence.ButlerDataRef`
1274 The data references to the input warped exposures.
1275 imageScalerList : `list` of `lsst.pipe.task.ImageScaler`
1276 The image scalars correct
for the zero point of the exposures.
1277 spanSetMaskList : `list` of `dict` containing spanSet lists,
or None
1278 Each element
is dict
with keys = mask plane name to add the spans to
1282 subExposures : `dict`
1283 The `dict` keys are the visit IDs,
1284 and the values are `lsst.afw.image.ExposureF`
1285 The pre-loaded exposures
for the current subregion.
1286 The variance plane contains weights,
and not the variance
1288 tempExpName = self.getTempExpDatasetName(self.warpType)
1289 zipIterables = zip(warpRefList, imageScalerList, spanSetMaskList)
1291 for warpExpRef, imageScaler, altMaskSpans
in zipIterables:
1292 if isinstance(warpExpRef, DeferredDatasetHandle):
1293 exposure = warpExpRef.get(parameters={
'bbox': bbox})
1295 exposure = warpExpRef.get(tempExpName +
"_sub", bbox=bbox)
1296 visit = warpExpRef.dataId[
"visit"]
1297 if altMaskSpans
is not None:
1298 self.applyAltMaskPlanes(exposure.mask, altMaskSpans)
1299 imageScaler.scaleMaskedImage(exposure.maskedImage)
1301 exposure.variance.array[:, :] = 0.
1303 exposure.variance.array[(exposure.mask.array & statsCtrl.getAndMask()) == 0] = 1.
1306 exposure.image.array[(exposure.mask.array & statsCtrl.getAndMask()) > 0] = 0.
1307 subExposures[visit] = exposure
1311 """Compute the PSF of the coadd from the exposures with the best seeing.
1315 templateCoadd : `lsst.afw.image.ExposureF`
1316 The initial coadd exposure before accounting for DCR.
1317 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
1318 `lsst.daf.persistence.ButlerDataRef`
1319 The data references to the input warped exposures.
1324 The average PSF of the input exposures
with the best seeing.
1326 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
1327 tempExpName = self.getTempExpDatasetName(self.warpType)
1330 ccds = templateCoadd.getInfo().getCoaddInputs().ccds
1331 templatePsf = templateCoadd.getPsf()
1333 templateAvgPos = templatePsf.getAveragePosition()
1334 psfRefSize = templatePsf.computeShape(templateAvgPos).getDeterminantRadius()*sigma2fwhm
1335 psfSizes = np.zeros(len(ccds))
1336 ccdVisits = np.array(ccds[
"visit"])
1337 for warpExpRef
in warpRefList:
1338 if isinstance(warpExpRef, DeferredDatasetHandle):
1340 psf = warpExpRef.get(component=
"psf")
1343 psf = warpExpRef.get(tempExpName).getPsf()
1344 visit = warpExpRef.dataId[
"visit"]
1345 psfAvgPos = psf.getAveragePosition()
1346 psfSize = psf.computeShape(psfAvgPos).getDeterminantRadius()*sigma2fwhm
1347 psfSizes[ccdVisits == visit] = psfSize
1351 sizeThreshold = min(np.median(psfSizes), psfRefSize)
1352 goodPsfs = psfSizes <= sizeThreshold
1353 psf = measAlg.CoaddPsf(ccds[goodPsfs], templateCoadd.getWcs(),
1354 self.config.coaddPsf.makeControl())
def makeSupplementaryDataGen3(self, butlerQC, inputRefs, outputRefs)
def makeSkyInfo(skyMap, tractId, patchId)
def loadSubExposures(self, bbox, statsCtrl, warpRefList, imageScalerList, spanSetMaskList)
def fillCoadd(self, dcrModels, skyInfo, warpRefList, weightList, calibration=None, coaddInputs=None, mask=None, variance=None)
def applyModelWeights(self, modelImages, refImage, modelWeights)
def calculateSingleConvergence(self, dcrModels, exposure, significanceImage, statsCtrl)
def stackCoadd(self, dcrCoadds)
def calculateConvergence(self, dcrModels, subExposures, bbox, warpRefList, weightList, statsCtrl)
def dcrAssembleSubregion(self, dcrModels, subExposures, bbox, dcrBBox, warpRefList, statsCtrl, convergenceMetric, gain, modelWeights, refImage, dcrWeights)
def calculateGain(self, convergenceList, gainList)
def calculateModelWeights(self, dcrModels, dcrBBox)
def newModelFromResidual(self, dcrModels, residualGeneratorList, dcrBBox, statsCtrl, gain, modelWeights, refImage, dcrWeights)
def selectCoaddPsf(self, templateCoadd, warpRefList)
def dcrResiduals(self, residual, visitInfo, wcs, effectiveWavelength, bandwidth)