25from scipy
import ndimage
30from lsst.daf.butler
import DeferredDatasetHandle
36import lsst.utils
as utils
38from .assembleCoadd
import (AssembleCoaddTask,
39 CompareWarpAssembleCoaddConfig,
40 CompareWarpAssembleCoaddTask)
41from .coaddBase
import makeSkyInfo
42from .measurePsf
import MeasurePsfTask
44__all__ = [
"DcrAssembleCoaddConnections",
"DcrAssembleCoaddTask",
"DcrAssembleCoaddConfig"]
48 dimensions=(
"tract",
"patch",
"band",
"skymap"),
49 defaultTemplates={
"inputCoaddName":
"deep",
50 "outputCoaddName":
"dcr",
54 inputWarps = pipeBase.connectionTypes.Input(
55 doc=(
"Input list of warps to be assembled i.e. stacked."
56 "WarpType (e.g. direct, psfMatched) is controlled by the warpType config parameter"),
57 name=
"{inputCoaddName}Coadd_{warpType}Warp",
58 storageClass=
"ExposureF",
59 dimensions=(
"tract",
"patch",
"skymap",
"visit",
"instrument"),
63 skyMap = pipeBase.connectionTypes.Input(
64 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
65 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
66 storageClass=
"SkyMap",
67 dimensions=(
"skymap", ),
69 brightObjectMask = pipeBase.connectionTypes.PrerequisiteInput(
70 doc=(
"Input Bright Object Mask mask produced with external catalogs to be applied to the mask plane"
72 name=
"brightObjectMask",
73 storageClass=
"ObjectMaskCatalog",
74 dimensions=(
"tract",
"patch",
"skymap",
"band"),
76 templateExposure = pipeBase.connectionTypes.Input(
77 doc=
"Input coadded exposure, produced by previous call to AssembleCoadd",
78 name=
"{fakesType}{inputCoaddName}Coadd{warpTypeSuffix}",
79 storageClass=
"ExposureF",
80 dimensions=(
"tract",
"patch",
"skymap",
"band"),
82 dcrCoadds = pipeBase.connectionTypes.Output(
83 doc=
"Output coadded exposure, produced by stacking input warps",
84 name=
"{fakesType}{outputCoaddName}Coadd{warpTypeSuffix}",
85 storageClass=
"ExposureF",
86 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
89 dcrNImages = pipeBase.connectionTypes.Output(
90 doc=
"Output image of number of input images per pixel",
91 name=
"{outputCoaddName}Coadd_nImage",
92 storageClass=
"ImageU",
93 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
97 def __init__(self, *, config=None):
98 super().__init__(config=config)
99 if not config.doWrite:
100 self.outputs.remove(
"dcrCoadds")
104 pipelineConnections=DcrAssembleCoaddConnections):
105 dcrNumSubfilters = pexConfig.Field(
107 doc=
"Number of sub-filters to forward model chromatic effects to fit the supplied exposures.",
110 maxNumIter = pexConfig.Field(
113 doc=
"Maximum number of iterations of forward modeling.",
116 minNumIter = pexConfig.Field(
119 doc=
"Minimum number of iterations of forward modeling.",
122 convergenceThreshold = pexConfig.Field(
124 doc=
"Target relative change in convergence between iterations of forward modeling.",
127 useConvergence = pexConfig.Field(
129 doc=
"Use convergence test as a forward modeling end condition?"
130 "If not set, skips calculating convergence and runs for ``maxNumIter`` iterations",
133 baseGain = pexConfig.Field(
136 doc=
"Relative weight to give the new solution vs. the last solution when updating the model."
137 "A value of 1.0 gives equal weight to both solutions."
138 "Small values imply slower convergence of the solution, but can "
139 "help prevent overshooting and failures in the fit."
140 "If ``baseGain`` is None, a conservative gain "
141 "will be calculated from the number of subfilters. ",
144 useProgressiveGain = pexConfig.Field(
146 doc=
"Use a gain that slowly increases above ``baseGain`` to accelerate convergence? "
147 "When calculating the next gain, we use up to 5 previous gains and convergence values."
148 "Can be set to False to force the model to change at the rate of ``baseGain``. ",
151 doAirmassWeight = pexConfig.Field(
153 doc=
"Weight exposures by airmass? Useful if there are relatively few high-airmass observations.",
156 modelWeightsWidth = pexConfig.Field(
158 doc=
"Width of the region around detected sources to include in the DcrModel.",
161 useModelWeights = pexConfig.Field(
163 doc=
"Width of the region around detected sources to include in the DcrModel.",
166 splitSubfilters = pexConfig.Field(
168 doc=
"Calculate DCR for two evenly-spaced wavelengths in each subfilter."
169 "Instead of at the midpoint",
172 splitThreshold = pexConfig.Field(
174 doc=
"Minimum DCR difference within a subfilter to use ``splitSubfilters``, in pixels."
175 "Set to 0 to always split the subfilters.",
178 regularizeModelIterations = pexConfig.Field(
180 doc=
"Maximum relative change of the model allowed between iterations."
181 "Set to zero to disable.",
184 regularizeModelFrequency = pexConfig.Field(
186 doc=
"Maximum relative change of the model allowed between subfilters."
187 "Set to zero to disable.",
190 convergenceMaskPlanes = pexConfig.ListField(
192 default=[
"DETECTED"],
193 doc=
"Mask planes to use to calculate convergence."
195 regularizationWidth = pexConfig.Field(
198 doc=
"Minimum radius of a region to include in regularization, in pixels."
200 imageInterpOrder = pexConfig.Field(
202 doc=
"The order of the spline interpolation used to shift the image plane.",
205 accelerateModel = pexConfig.Field(
207 doc=
"Factor to amplify the differences between model planes by to speed convergence.",
210 doCalculatePsf = pexConfig.Field(
212 doc=
"Set to detect stars and recalculate the PSF from the final coadd."
213 "Otherwise the PSF is estimated from a selection of the best input exposures",
216 detectPsfSources = pexConfig.ConfigurableField(
217 target=measAlg.SourceDetectionTask,
218 doc=
"Task to detect sources for PSF measurement, if ``doCalculatePsf`` is set.",
220 measurePsfSources = pexConfig.ConfigurableField(
221 target=SingleFrameMeasurementTask,
222 doc=
"Task to measure sources for PSF measurement, if ``doCalculatePsf`` is set."
224 measurePsf = pexConfig.ConfigurableField(
225 target=MeasurePsfTask,
226 doc=
"Task to measure the PSF of the coadd, if ``doCalculatePsf`` is set.",
228 effectiveWavelength = pexConfig.Field(
229 doc=
"Effective wavelength of the filter, in nm."
230 "Required if transmission curves aren't used."
231 "Support for using transmission curves is to be added in DM-13668.",
234 bandwidth = pexConfig.Field(
235 doc=
"Bandwidth of the physical filter, in nm."
236 "Required if transmission curves aren't used."
237 "Support for using transmission curves is to be added in DM-13668.",
241 def setDefaults(self):
242 CompareWarpAssembleCoaddConfig.setDefaults(self)
243 self.assembleStaticSkyModel.retarget(CompareWarpAssembleCoaddTask)
245 self.assembleStaticSkyModel.warpType = self.warpType
247 self.assembleStaticSkyModel.doNImage =
False
248 self.assembleStaticSkyModel.doWrite =
False
249 self.detectPsfSources.returnOriginalFootprints =
False
250 self.detectPsfSources.thresholdPolarity =
"positive"
252 self.detectPsfSources.thresholdValue = 50
253 self.detectPsfSources.nSigmaToGrow = 2
255 self.detectPsfSources.minPixels = 25
257 self.detectPsfSources.thresholdType =
"pixel_stdev"
260 self.measurePsf.starSelector[
"objectSize"].doFluxLimit =
False
264 """Assemble DCR coadded images from a set of warps.
269 The number of pixels to grow each subregion by to allow for DCR.
273 As
with AssembleCoaddTask, we want to assemble a coadded image
from a set of
274 Warps (also called coadded temporary exposures), including the effects of
275 Differential Chromatic Refraction (DCR).
276 For full details of the mathematics
and algorithm, please see
277 DMTN-037: DCR-matched template generation (https://dmtn-037.lsst.io).
279 This Task produces a DCR-corrected deepCoadd,
as well
as a dcrCoadd
for
280 each subfilter used
in the iterative calculation.
281 It begins by dividing the bandpass-defining filter into N equal bandwidth
282 "subfilters",
and divides the flux
in each pixel
from an initial coadd
283 equally into each
as a
"dcrModel". Because the airmass
and parallactic
284 angle of each individual exposure
is known, we can calculate the shift
285 relative to the center of the band
in each subfilter due to DCR. For each
286 exposure we apply this shift
as a linear transformation to the dcrModels
287 and stack the results to produce a DCR-matched exposure. The matched
288 exposures are subtracted
from the input exposures to produce a set of
289 residual images,
and these residuals are reverse shifted
for each
290 exposures
' subfilters and stacked. The shifted and stacked residuals are
291 added to the dcrModels to produce a new estimate of the flux in each pixel
292 within each subfilter. The dcrModels are solved
for iteratively, which
293 continues until the solution
from a new iteration improves by less than
294 a set percentage,
or a maximum number of iterations
is reached.
295 Two forms of regularization are employed to reduce unphysical results.
296 First, the new solution
is averaged
with the solution
from the previous
297 iteration, which mitigates oscillating solutions where the model
298 overshoots
with alternating very high
and low values.
299 Second, a common degeneracy when the data have a limited range of airmass
or
300 parallactic angle values
is for one subfilter to be fit
with very low
or
301 negative values,
while another subfilter
is fit
with very high values. This
302 typically appears
in the form of holes next to sources
in one subfilter,
303 and corresponding extended wings
in another. Because each subfilter has
304 a narrow bandwidth we assume that physical sources that are above the noise
305 level will
not vary
in flux by more than a factor of `frequencyClampFactor`
306 between subfilters,
and pixels that have flux deviations larger than that
307 factor will have the excess flux distributed evenly among all subfilters.
308 If `splitSubfilters`
is set, then each subfilter will be further sub-
309 divided during the forward modeling step (only). This approximates using
310 a higher number of subfilters that may be necessary
for high airmass
311 observations, but does
not increase the number of free parameters
in the
312 fit. This
is needed when there are high airmass observations which would
313 otherwise have significant DCR even within a subfilter. Because calculating
314 the shifted images takes most of the time, splitting the subfilters
is
315 turned off by way of the `splitThreshold` option
for low-airmass
316 observations that do
not suffer
from DCR within a subfilter.
319 ConfigClass = DcrAssembleCoaddConfig
320 _DefaultName = "dcrAssembleCoadd"
322 def __init__(self, *args, **kwargs):
323 super().__init__(*args, **kwargs)
324 if self.config.doCalculatePsf:
325 self.schema = afwTable.SourceTable.makeMinimalSchema()
326 self.makeSubtask(
"detectPsfSources", schema=self.schema)
327 self.makeSubtask(
"measurePsfSources", schema=self.schema)
328 self.makeSubtask(
"measurePsf", schema=self.schema)
330 @utils.inheritDoc(pipeBase.PipelineTask)
331 def runQuantum(self, butlerQC, inputRefs, outputRefs):
336 Assemble a coadd from a set of Warps.
338 PipelineTask (Gen3) entry point to Coadd a set of Warps.
339 Analogous to `runDataRef`, it prepares all the data products to be
340 passed to `run`,
and processes the results before returning a struct
341 of results to be written out. AssembleCoadd cannot fit all Warps
in memory.
342 Therefore, its inputs are accessed subregion by subregion
343 by the Gen3 `DeferredDatasetHandle` that
is analagous to the Gen2
344 `lsst.daf.persistence.ButlerDataRef`. Any updates to this method should
345 correspond to an update
in `runDataRef`
while both entry points
348 inputData = butlerQC.get(inputRefs)
352 skyMap = inputData[
"skyMap"]
353 outputDataId = butlerQC.quantum.dataId
356 tractId=outputDataId[
'tract'],
357 patchId=outputDataId[
'patch'])
361 warpRefList = inputData[
'inputWarps']
363 inputs = self.prepareInputs(warpRefList)
364 self.log.info(
"Found %d %s", len(inputs.tempExpRefList),
365 self.getTempExpDatasetName(self.warpType))
366 if len(inputs.tempExpRefList) == 0:
367 self.log.warning(
"No coadd temporary exposures found")
370 supplementaryData = self.makeSupplementaryDataGen3(butlerQC, inputRefs, outputRefs)
371 retStruct = self.run(inputData[
'skyInfo'], inputs.tempExpRefList, inputs.imageScalerList,
372 inputs.weightList, supplementaryData=supplementaryData)
374 inputData.setdefault(
'brightObjectMask',
None)
375 for subfilter
in range(self.config.dcrNumSubfilters):
377 retStruct.dcrCoadds[subfilter].setPsf(retStruct.coaddExposure.getPsf())
378 self.processResults(retStruct.dcrCoadds[subfilter], inputData[
'brightObjectMask'], outputDataId)
380 if self.config.doWrite:
381 butlerQC.put(retStruct, outputRefs)
385 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
386 """Assemble a coadd from a set of warps.
388 Coadd a set of Warps. Compute weights to be applied to each Warp and
389 find scalings to match the photometric zeropoint to a reference Warp.
390 Assemble the Warps using run method.
391 Forward model chromatic effects across multiple subfilters,
392 and subtract
from the input Warps to build sets of residuals.
393 Use the residuals to construct a new ``DcrModel``
for each subfilter,
394 and iterate until the model converges.
395 Interpolate over NaNs
and optionally write the coadd to disk.
396 Return the coadded exposure.
400 dataRef : `lsst.daf.persistence.ButlerDataRef`
401 Data reference defining the patch
for coaddition
and the
403 selectDataList : `list` of `lsst.daf.persistence.ButlerDataRef`
404 List of data references to warps. Data to be coadded will be
405 selected
from this list based on overlap
with the patch defined by
410 results : `lsst.pipe.base.Struct`
411 The Struct contains the following fields:
414 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`)
415 - ``dcrCoadds``: `list` of coadded exposures
for each subfilter
416 - ``dcrNImages``: `list` of exposure count images
for each subfilter
418 if (selectDataList
is None and warpRefList
is None)
or (selectDataList
and warpRefList):
419 raise RuntimeError(
"runDataRef must be supplied either a selectDataList or warpRefList")
421 skyInfo = self.getSkyInfo(dataRef)
422 if warpRefList
is None:
423 calExpRefList = self.selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
424 if len(calExpRefList) == 0:
425 self.log.warning(
"No exposures to coadd")
427 self.log.info(
"Coadding %d exposures", len(calExpRefList))
429 warpRefList = self.getTempExpRefList(dataRef, calExpRefList)
431 inputData = self.prepareInputs(warpRefList)
432 self.log.info(
"Found %d %s", len(inputData.tempExpRefList),
433 self.getTempExpDatasetName(self.warpType))
434 if len(inputData.tempExpRefList) == 0:
435 self.log.warning(
"No coadd temporary exposures found")
438 supplementaryData = self.makeSupplementaryData(dataRef, warpRefList=inputData.tempExpRefList)
440 results = self.run(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
441 inputData.weightList, supplementaryData=supplementaryData)
443 self.log.warning(
"Could not construct DcrModel for patch %s: no data to coadd.",
444 skyInfo.patchInfo.getIndex())
447 if self.config.doCalculatePsf:
448 self.measureCoaddPsf(results.coaddExposure)
449 brightObjects = self.readBrightObjectMasks(dataRef)
if self.config.doMaskBrightObjects
else None
450 for subfilter
in range(self.config.dcrNumSubfilters):
452 results.dcrCoadds[subfilter].setPsf(results.coaddExposure.getPsf())
453 self.processResults(results.dcrCoadds[subfilter],
454 brightObjectMasks=brightObjects, dataId=dataRef.dataId)
455 if self.config.doWrite:
456 self.log.info(
"Persisting dcrCoadd")
457 dataRef.put(results.dcrCoadds[subfilter],
"dcrCoadd", subfilter=subfilter,
458 numSubfilters=self.config.dcrNumSubfilters)
459 if self.config.doNImage
and results.dcrNImages
is not None:
460 dataRef.put(results.dcrNImages[subfilter],
"dcrCoadd_nImage", subfilter=subfilter,
461 numSubfilters=self.config.dcrNumSubfilters)
465 @utils.inheritDoc(AssembleCoaddTask)
467 """Load the previously-generated template coadd.
469 This can be removed entirely once we no longer support the Gen 2 butler.
473 templateCoadd : `lsst.pipe.base.Struct`
474 Result struct with components:
476 - ``templateCoadd``: coadded exposure (`lsst.afw.image.ExposureF`)
478 templateCoadd = butlerQC.get(inputRefs.templateExposure)
480 return pipeBase.Struct(templateCoadd=templateCoadd)
482 def measureCoaddPsf(self, coaddExposure):
483 """Detect sources on the coadd exposure and measure the final PSF.
488 The final coadded exposure.
490 table = afwTable.SourceTable.make(self.schema)
491 detResults = self.detectPsfSources.run(table, coaddExposure, clearMask=False)
492 coaddSources = detResults.sources
493 self.measurePsfSources.run(
494 measCat=coaddSources,
495 exposure=coaddExposure
502 psfResults = self.measurePsf.run(coaddExposure, coaddSources)
503 except Exception
as e:
504 self.log.warning(
"Unable to calculate PSF, using default coadd PSF: %s", e)
506 coaddExposure.setPsf(psfResults.psf)
508 def prepareDcrInputs(self, templateCoadd, warpRefList, weightList):
509 """Prepare the DCR coadd by iterating through the visitInfo of the input warps.
511 Sets the property ``bufferSize``.
515 templateCoadd : `lsst.afw.image.ExposureF`
516 The initial coadd exposure before accounting for DCR.
517 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
518 `lsst.daf.persistence.ButlerDataRef`
519 The data references to the input warped exposures.
520 weightList : `list` of `float`
521 The weight to give each input exposure
in the coadd
522 Will be modified
in place
if ``doAirmassWeight``
is set.
526 dcrModels : `lsst.pipe.tasks.DcrModel`
527 Best fit model of the true sky after correcting chromatic effects.
532 If ``lambdaMin``
is missing
from the Mapper
class of the obs package being used.
534 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
535 filterLabel = templateCoadd.getFilterLabel()
536 tempExpName = self.getTempExpDatasetName(self.warpType)
541 for visitNum, warpExpRef
in enumerate(warpRefList):
542 if isinstance(warpExpRef, DeferredDatasetHandle):
544 visitInfo = warpExpRef.get(component=
"visitInfo")
545 psf = warpExpRef.get(component=
"psf")
548 visitInfo = warpExpRef.get(tempExpName +
"_visitInfo")
549 psf = warpExpRef.get(tempExpName).getPsf()
550 visit = warpExpRef.dataId[
"visit"]
551 psfSize = psf.computeShape().getDeterminantRadius()*sigma2fwhm
552 airmass = visitInfo.getBoresightAirmass()
553 parallacticAngle = visitInfo.getBoresightParAngle().asDegrees()
554 airmassDict[visit] = airmass
555 angleDict[visit] = parallacticAngle
556 psfSizeDict[visit] = psfSize
557 if self.config.doAirmassWeight:
558 weightList[visitNum] *= airmass
559 dcrShifts.append(np.max(np.abs(calculateDcr(visitInfo, templateCoadd.getWcs(),
560 self.config.effectiveWavelength,
561 self.config.bandwidth,
562 self.config.dcrNumSubfilters))))
563 self.log.info(
"Selected airmasses:\n%s", airmassDict)
564 self.log.info(
"Selected parallactic angles:\n%s", angleDict)
565 self.log.info(
"Selected PSF sizes:\n%s", psfSizeDict)
566 self.bufferSize = int(np.ceil(np.max(dcrShifts)) + 1)
568 psf = self.selectCoaddPsf(templateCoadd, warpRefList)
569 except Exception
as e:
570 self.log.warning(
"Unable to calculate restricted PSF, using default coadd PSF: %s", e)
572 psf = templateCoadd.getPsf()
573 dcrModels = DcrModel.fromImage(templateCoadd.maskedImage,
574 self.config.dcrNumSubfilters,
575 effectiveWavelength=self.config.effectiveWavelength,
576 bandwidth=self.config.bandwidth,
577 filterLabel=filterLabel,
582 def run(self, skyInfo, warpRefList, imageScalerList, weightList,
583 supplementaryData=None):
584 """Assemble the coadd.
586 Requires additional inputs Struct ``supplementaryData`` to contain a
587 ``templateCoadd`` that serves as the model of the static sky.
589 Find artifacts
and apply them to the warps
' masks creating a list of
590 alternative masks with a new
"CLIPPED" plane
and updated
"NO_DATA" plane
591 Then
pass these alternative masks to the base
class's assemble method.
593 Divide the ``templateCoadd`` evenly between each subfilter of a
594 ``DcrModel`` as the starting best estimate of the true wavelength-
595 dependent sky. Forward model the ``DcrModel`` using the known
596 chromatic effects
in each subfilter
and calculate a convergence metric
597 based on how well the modeled template matches the input warps. If
598 the convergence has
not yet reached the desired threshold, then shift
599 and stack the residual images to build a new ``DcrModel``. Apply
600 conditioning to prevent oscillating solutions between iterations
or
603 Once the ``DcrModel`` reaches convergence
or the maximum number of
604 iterations has been reached, fill the metadata
for each subfilter
605 image
and make them proper ``coaddExposure``s.
609 skyInfo : `lsst.pipe.base.Struct`
610 Patch geometry information,
from getSkyInfo
611 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
612 `lsst.daf.persistence.ButlerDataRef`
613 The data references to the input warped exposures.
614 imageScalerList : `list` of `lsst.pipe.task.ImageScaler`
615 The image scalars correct
for the zero point of the exposures.
616 weightList : `list` of `float`
617 The weight to give each input exposure
in the coadd
618 supplementaryData : `lsst.pipe.base.Struct`
619 Result struct returned by ``makeSupplementaryData``
with components:
625 result : `lsst.pipe.base.Struct`
626 Result struct
with components:
629 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`)
630 - ``dcrCoadds``: `list` of coadded exposures
for each subfilter
631 - ``dcrNImages``: `list` of exposure count images
for each subfilter
633 minNumIter = self.config.minNumIter or self.config.dcrNumSubfilters
634 maxNumIter = self.config.maxNumIter
or self.config.dcrNumSubfilters*3
635 templateCoadd = supplementaryData.templateCoadd
636 baseMask = templateCoadd.mask.clone()
639 baseVariance = templateCoadd.variance.clone()
640 baseVariance /= self.config.dcrNumSubfilters
641 spanSetMaskList = self.findArtifacts(templateCoadd, warpRefList, imageScalerList)
643 templateCoadd.setMask(baseMask)
644 badMaskPlanes = self.config.badMaskPlanes[:]
649 badPixelMask = templateCoadd.mask.getPlaneBitMask(badMaskPlanes)
651 stats = self.prepareStats(mask=badPixelMask)
652 dcrModels = self.prepareDcrInputs(templateCoadd, warpRefList, weightList)
653 if self.config.doNImage:
654 dcrNImages, dcrWeights = self.calculateNImage(dcrModels, skyInfo.bbox, warpRefList,
655 spanSetMaskList, stats.ctrl)
656 nImage = afwImage.ImageU(skyInfo.bbox)
660 for dcrNImage
in dcrNImages:
666 nSubregions = (ceil(skyInfo.bbox.getHeight()/subregionSize[1])
667 * ceil(skyInfo.bbox.getWidth()/subregionSize[0]))
669 for subBBox
in self._subBBoxIter(skyInfo.bbox, subregionSize):
672 self.log.info(
"Computing coadd over patch %s subregion %s of %s: %s",
673 skyInfo.patchInfo.getIndex(), subIter, nSubregions, subBBox)
675 dcrBBox.grow(self.bufferSize)
676 dcrBBox.clip(dcrModels.bbox)
677 modelWeights = self.calculateModelWeights(dcrModels, dcrBBox)
678 subExposures = self.loadSubExposures(dcrBBox, stats.ctrl, warpRefList,
679 imageScalerList, spanSetMaskList)
680 convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
681 warpRefList, weightList, stats.ctrl)
682 self.log.info(
"Initial convergence : %s", convergenceMetric)
683 convergenceList = [convergenceMetric]
685 convergenceCheck = 1.
686 refImage = templateCoadd.image
687 while (convergenceCheck > self.config.convergenceThreshold
or modelIter <= minNumIter):
688 gain = self.calculateGain(convergenceList, gainList)
689 self.dcrAssembleSubregion(dcrModels, subExposures, subBBox, dcrBBox, warpRefList,
690 stats.ctrl, convergenceMetric, gain,
691 modelWeights, refImage, dcrWeights)
692 if self.config.useConvergence:
693 convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
694 warpRefList, weightList, stats.ctrl)
695 if convergenceMetric == 0:
696 self.log.warning(
"Coadd patch %s subregion %s had convergence metric of 0.0 which is "
697 "most likely due to there being no valid data in the region.",
698 skyInfo.patchInfo.getIndex(), subIter)
700 convergenceCheck = (convergenceList[-1] - convergenceMetric)/convergenceMetric
701 if (convergenceCheck < 0) & (modelIter > minNumIter):
702 self.log.warning(
"Coadd patch %s subregion %s diverged before reaching maximum "
703 "iterations or desired convergence improvement of %s."
705 skyInfo.patchInfo.getIndex(), subIter,
706 self.config.convergenceThreshold, convergenceCheck)
708 convergenceList.append(convergenceMetric)
709 if modelIter > maxNumIter:
710 if self.config.useConvergence:
711 self.log.warning(
"Coadd patch %s subregion %s reached maximum iterations "
712 "before reaching desired convergence improvement of %s."
713 " Final convergence improvement: %s",
714 skyInfo.patchInfo.getIndex(), subIter,
715 self.config.convergenceThreshold, convergenceCheck)
718 if self.config.useConvergence:
719 self.log.info(
"Iteration %s with convergence metric %s, %.4f%% improvement (gain: %.2f)",
720 modelIter, convergenceMetric, 100.*convergenceCheck, gain)
723 if self.config.useConvergence:
724 self.log.info(
"Coadd patch %s subregion %s finished with "
725 "convergence metric %s after %s iterations",
726 skyInfo.patchInfo.getIndex(), subIter, convergenceMetric, modelIter)
728 self.log.info(
"Coadd patch %s subregion %s finished after %s iterations",
729 skyInfo.patchInfo.getIndex(), subIter, modelIter)
730 if self.config.useConvergence
and convergenceMetric > 0:
731 self.log.info(
"Final convergence improvement was %.4f%% overall",
732 100*(convergenceList[0] - convergenceMetric)/convergenceMetric)
734 dcrCoadds = self.fillCoadd(dcrModels, skyInfo, warpRefList, weightList,
735 calibration=self.scaleZeroPoint.getPhotoCalib(),
736 coaddInputs=templateCoadd.getInfo().getCoaddInputs(),
738 variance=baseVariance)
739 coaddExposure = self.stackCoadd(dcrCoadds)
740 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
741 dcrCoadds=dcrCoadds, dcrNImages=dcrNImages)
743 def calculateNImage(self, dcrModels, bbox, warpRefList, spanSetMaskList, statsCtrl):
744 """Calculate the number of exposures contributing to each subfilter.
748 dcrModels : `lsst.pipe.tasks.DcrModel`
749 Best fit model of the true sky after correcting chromatic effects.
750 bbox : `lsst.geom.box.Box2I`
751 Bounding box of the patch to coadd.
752 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
753 `lsst.daf.persistence.ButlerDataRef`
754 The data references to the input warped exposures.
755 spanSetMaskList : `list` of `dict` containing spanSet lists,
or None
756 Each element of the `dict` contains the new mask plane name
757 (e.g.
"CLIPPED and/or "NO_DATA
") as the key,
758 and the list of SpanSets to apply to the mask.
760 Statistics control object
for coadd
764 dcrNImages : `list` of `lsst.afw.image.ImageU`
765 List of exposure count images
for each subfilter
766 dcrWeights : `list` of `lsst.afw.image.ImageF`
767 Per-pixel weights
for each subfilter.
768 Equal to 1/(number of unmasked images contributing to each pixel).
770 dcrNImages = [afwImage.ImageU(bbox) for subfilter
in range(self.config.dcrNumSubfilters)]
771 dcrWeights = [afwImage.ImageF(bbox)
for subfilter
in range(self.config.dcrNumSubfilters)]
772 tempExpName = self.getTempExpDatasetName(self.warpType)
773 for warpExpRef, altMaskSpans
in zip(warpRefList, spanSetMaskList):
774 if isinstance(warpExpRef, DeferredDatasetHandle):
776 exposure = warpExpRef.get(parameters={
'bbox': bbox})
779 exposure = warpExpRef.get(tempExpName +
"_sub", bbox=bbox)
780 visitInfo = exposure.getInfo().getVisitInfo()
781 wcs = exposure.getInfo().getWcs()
783 if altMaskSpans
is not None:
784 self.applyAltMaskPlanes(mask, altMaskSpans)
785 weightImage = np.zeros_like(exposure.image.array)
786 weightImage[(mask.array & statsCtrl.getAndMask()) == 0] = 1.
789 weightsGenerator = self.dcrResiduals(weightImage, visitInfo, wcs,
790 dcrModels.effectiveWavelength, dcrModels.bandwidth)
791 for shiftedWeights, dcrNImage, dcrWeight
in zip(weightsGenerator, dcrNImages, dcrWeights):
792 dcrNImage.array += np.rint(shiftedWeights).astype(dcrNImage.array.dtype)
793 dcrWeight.array += shiftedWeights
795 weightsThreshold = 1.
796 goodPix = dcrWeights[0].array > weightsThreshold
797 for weights
in dcrWeights[1:]:
798 goodPix = (weights.array > weightsThreshold) & goodPix
799 for subfilter
in range(self.config.dcrNumSubfilters):
800 dcrWeights[subfilter].array[goodPix] = 1./dcrWeights[subfilter].array[goodPix]
801 dcrWeights[subfilter].array[~goodPix] = 0.
802 dcrNImages[subfilter].array[~goodPix] = 0
803 return (dcrNImages, dcrWeights)
806 statsCtrl, convergenceMetric,
807 gain, modelWeights, refImage, dcrWeights):
808 """Assemble the DCR coadd for a sub-region.
810 Build a DCR-matched template for each input exposure, then shift the
811 residuals according to the DCR
in each subfilter.
812 Stack the shifted residuals
and apply them
as a correction to the
813 solution
from the previous iteration.
814 Restrict the new model solutions
from varying by more than a factor of
815 `modelClampFactor`
from the last solution,
and additionally restrict the
816 individual subfilter models
from varying by more than a factor of
817 `frequencyClampFactor`
from their average.
818 Finally, mitigate potentially oscillating solutions by averaging the new
819 solution
with the solution
from the previous iteration, weighted by
820 their convergence metric.
824 dcrModels : `lsst.pipe.tasks.DcrModel`
825 Best fit model of the true sky after correcting chromatic effects.
826 subExposures : `dict` of `lsst.afw.image.ExposureF`
827 The pre-loaded exposures
for the current subregion.
828 bbox : `lsst.geom.box.Box2I`
829 Bounding box of the subregion to coadd.
830 dcrBBox : `lsst.geom.box.Box2I`
831 Sub-region of the coadd which includes a buffer to allow
for DCR.
832 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
833 `lsst.daf.persistence.ButlerDataRef`
834 The data references to the input warped exposures.
836 Statistics control object
for coadd
837 convergenceMetric : `float`
838 Quality of fit metric
for the matched templates of the input images.
839 gain : `float`, optional
840 Relative weight to give the new solution when updating the model.
841 modelWeights : `numpy.ndarray`
or `float`
842 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
843 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
845 A reference image used to supply the default pixel values.
847 Per-pixel weights
for each subfilter.
848 Equal to 1/(number of unmasked images contributing to each pixel).
850 residualGeneratorList = []
852 for warpExpRef
in warpRefList:
853 visit = warpExpRef.dataId[
"visit"]
854 exposure = subExposures[visit]
855 visitInfo = exposure.getInfo().getVisitInfo()
856 wcs = exposure.getInfo().getWcs()
857 templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
858 order=self.config.imageInterpOrder,
859 splitSubfilters=self.config.splitSubfilters,
860 splitThreshold=self.config.splitThreshold,
861 amplifyModel=self.config.accelerateModel)
862 residual = exposure.image.array - templateImage.array
864 residual *= exposure.variance.array
868 residualGeneratorList.append(self.dcrResiduals(residual, visitInfo, wcs,
869 dcrModels.effectiveWavelength,
870 dcrModels.bandwidth))
872 dcrSubModelOut = self.newModelFromResidual(dcrModels, residualGeneratorList, dcrBBox, statsCtrl,
874 modelWeights=modelWeights,
876 dcrWeights=dcrWeights)
877 dcrModels.assign(dcrSubModelOut, bbox)
879 def dcrResiduals(self, residual, visitInfo, wcs, effectiveWavelength, bandwidth):
880 """Prepare a residual image for stacking in each subfilter by applying the reverse DCR shifts.
884 residual : `numpy.ndarray`
885 The residual masked image for one exposure,
886 after subtracting the matched template
888 Metadata
for the exposure.
890 Coordinate system definition (wcs)
for the exposure.
894 residualImage : `numpy.ndarray`
895 The residual image
for the next subfilter, shifted
for DCR.
899 filteredResidual = ndimage.spline_filter(residual, order=self.config.imageInterpOrder)
902 dcrShift = calculateDcr(visitInfo, wcs, effectiveWavelength, bandwidth, self.config.dcrNumSubfilters,
903 splitSubfilters=
False)
905 yield applyDcr(filteredResidual, dcr, useInverse=
True, splitSubfilters=
False,
906 doPrefilter=
False, order=self.config.imageInterpOrder)
909 gain, modelWeights, refImage, dcrWeights):
910 """Calculate a new DcrModel from a set of image residuals.
914 dcrModels : `lsst.pipe.tasks.DcrModel`
915 Current model of the true sky after correcting chromatic effects.
916 residualGeneratorList : `generator` of `numpy.ndarray`
917 The residual image for the next subfilter, shifted
for DCR.
918 dcrBBox : `lsst.geom.box.Box2I`
919 Sub-region of the coadd which includes a buffer to allow
for DCR.
921 Statistics control object
for coadd
923 Relative weight to give the new solution when updating the model.
924 modelWeights : `numpy.ndarray`
or `float`
925 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
926 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
928 A reference image used to supply the default pixel values.
930 Per-pixel weights
for each subfilter.
931 Equal to 1/(number of unmasked images contributing to each pixel).
935 dcrModel : `lsst.pipe.tasks.DcrModel`
936 New model of the true sky after correcting chromatic effects.
939 for subfilter, model
in enumerate(dcrModels):
940 residualsList = [next(residualGenerator)
for residualGenerator
in residualGeneratorList]
941 residual = np.sum(residualsList, axis=0)
942 residual *= dcrWeights[subfilter][dcrBBox].array
944 newModel = model[dcrBBox].clone()
945 newModel.array += residual
947 badPixels = ~np.isfinite(newModel.array)
948 newModel.array[badPixels] = model[dcrBBox].array[badPixels]
949 if self.config.regularizeModelIterations > 0:
950 dcrModels.regularizeModelIter(subfilter, newModel, dcrBBox,
951 self.config.regularizeModelIterations,
952 self.config.regularizationWidth)
953 newModelImages.append(newModel)
954 if self.config.regularizeModelFrequency > 0:
955 dcrModels.regularizeModelFreq(newModelImages, dcrBBox, statsCtrl,
956 self.config.regularizeModelFrequency,
957 self.config.regularizationWidth)
958 dcrModels.conditionDcrModel(newModelImages, dcrBBox, gain=gain)
959 self.applyModelWeights(newModelImages, refImage[dcrBBox], modelWeights)
960 return DcrModel(newModelImages, dcrModels.filter, dcrModels.effectiveWavelength,
961 dcrModels.bandwidth, dcrModels.psf,
962 dcrModels.mask, dcrModels.variance)
965 """Calculate a quality of fit metric for the matched templates.
969 dcrModels : `lsst.pipe.tasks.DcrModel`
970 Best fit model of the true sky after correcting chromatic effects.
971 subExposures : `dict` of `lsst.afw.image.ExposureF`
972 The pre-loaded exposures for the current subregion.
973 bbox : `lsst.geom.box.Box2I`
975 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
976 `lsst.daf.persistence.ButlerDataRef`
977 The data references to the input warped exposures.
978 weightList : `list` of `float`
979 The weight to give each input exposure
in the coadd
981 Statistics control object
for coadd
985 convergenceMetric : `float`
986 Quality of fit metric
for all input exposures, within the sub-region
988 significanceImage = np.abs(dcrModels.getReferenceImage(bbox))
990 significanceImage += nSigma*dcrModels.calculateNoiseCutoff(dcrModels[1], statsCtrl,
991 bufferSize=self.bufferSize)
992 if np.max(significanceImage) == 0:
993 significanceImage += 1.
997 for warpExpRef, expWeight
in zip(warpRefList, weightList):
998 visit = warpExpRef.dataId[
"visit"]
999 exposure = subExposures[visit][bbox]
1000 singleMetric = self.calculateSingleConvergence(dcrModels, exposure, significanceImage, statsCtrl)
1001 metric += singleMetric
1002 metricList[visit] = singleMetric
1004 self.log.info(
"Individual metrics:\n%s", metricList)
1005 return 1.0
if weight == 0.0
else metric/weight
1008 """Calculate a quality of fit metric for a single matched template.
1012 dcrModels : `lsst.pipe.tasks.DcrModel`
1013 Best fit model of the true sky after correcting chromatic effects.
1014 exposure : `lsst.afw.image.ExposureF`
1015 The input warped exposure to evaluate.
1016 significanceImage : `numpy.ndarray`
1017 Array of weights for each pixel corresponding to its significance
1018 for the convergence calculation.
1020 Statistics control object
for coadd
1024 convergenceMetric : `float`
1025 Quality of fit metric
for one exposure, within the sub-region.
1027 convergeMask = exposure.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
1028 templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
1029 order=self.config.imageInterpOrder,
1030 splitSubfilters=self.config.splitSubfilters,
1031 splitThreshold=self.config.splitThreshold,
1032 amplifyModel=self.config.accelerateModel)
1033 diffVals = np.abs(exposure.image.array - templateImage.array)*significanceImage
1034 refVals = np.abs(exposure.image.array + templateImage.array)*significanceImage/2.
1036 finitePixels = np.isfinite(diffVals)
1037 goodMaskPixels = (exposure.mask.array & statsCtrl.getAndMask()) == 0
1038 convergeMaskPixels = exposure.mask.array & convergeMask > 0
1039 usePixels = finitePixels & goodMaskPixels & convergeMaskPixels
1040 if np.sum(usePixels) == 0:
1043 diffUse = diffVals[usePixels]
1044 refUse = refVals[usePixels]
1045 metric = np.sum(diffUse/np.median(diffUse))/np.sum(refUse/np.median(diffUse))
1049 """Add a list of sub-band coadds together.
1053 dcrCoadds : `list` of `lsst.afw.image.ExposureF`
1054 A list of coadd exposures, each exposure containing
1055 the model for one subfilter.
1059 coaddExposure : `lsst.afw.image.ExposureF`
1060 A single coadd exposure that
is the sum of the sub-bands.
1062 coaddExposure = dcrCoadds[0].clone()
1063 for coadd
in dcrCoadds[1:]:
1064 coaddExposure.maskedImage += coadd.maskedImage
1065 return coaddExposure
1067 def fillCoadd(self, dcrModels, skyInfo, warpRefList, weightList, calibration=None, coaddInputs=None,
1068 mask=None, variance=None):
1069 """Create a list of coadd exposures from a list of masked images.
1073 dcrModels : `lsst.pipe.tasks.DcrModel`
1074 Best fit model of the true sky after correcting chromatic effects.
1075 skyInfo : `lsst.pipe.base.Struct`
1076 Patch geometry information, from getSkyInfo
1077 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
1078 `lsst.daf.persistence.ButlerDataRef`
1079 The data references to the input warped exposures.
1080 weightList : `list` of `float`
1081 The weight to give each input exposure
in the coadd
1082 calibration : `lsst.afw.Image.PhotoCalib`, optional
1083 Scale factor to set the photometric calibration of an exposure.
1084 coaddInputs : `lsst.afw.Image.CoaddInputs`, optional
1085 A record of the observations that are included
in the coadd.
1087 Optional mask to override the values
in the final coadd.
1089 Optional variance plane to override the values
in the final coadd.
1093 dcrCoadds : `list` of `lsst.afw.image.ExposureF`
1094 A list of coadd exposures, each exposure containing
1095 the model
for one subfilter.
1098 refModel = dcrModels.getReferenceImage()
1099 for model
in dcrModels:
1100 if self.config.accelerateModel > 1:
1101 model.array = (model.array - refModel)*self.config.accelerateModel + refModel
1102 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
1103 if calibration
is not None:
1104 coaddExposure.setPhotoCalib(calibration)
1105 if coaddInputs
is not None:
1106 coaddExposure.getInfo().setCoaddInputs(coaddInputs)
1108 self.assembleMetadata(coaddExposure, warpRefList, weightList)
1110 coaddExposure.setPsf(dcrModels.psf)
1111 coaddUtils.setCoaddEdgeBits(dcrModels.mask[skyInfo.bbox], dcrModels.variance[skyInfo.bbox])
1112 maskedImage = afwImage.MaskedImageF(dcrModels.bbox)
1113 maskedImage.image = model
1114 maskedImage.mask = dcrModels.mask
1115 maskedImage.variance = dcrModels.variance
1116 coaddExposure.setMaskedImage(maskedImage[skyInfo.bbox])
1117 coaddExposure.setPhotoCalib(self.scaleZeroPoint.getPhotoCalib())
1118 if mask
is not None:
1119 coaddExposure.setMask(mask)
1120 if variance
is not None:
1121 coaddExposure.setVariance(variance)
1122 dcrCoadds.append(coaddExposure)
1126 """Calculate the gain to use for the current iteration.
1128 After calculating a new DcrModel, each value is averaged
with the
1129 value
in the corresponding pixel
from the previous iteration. This
1130 reduces oscillating solutions that iterative techniques are plagued by,
1131 and speeds convergence. By far the biggest changes to the model
1132 happen
in the first couple iterations, so we can also use a more
1133 aggressive gain later when the model
is changing slowly.
1137 convergenceList : `list` of `float`
1138 The quality of fit metric
from each previous iteration.
1139 gainList : `list` of `float`
1140 The gains used
in each previous iteration: appended
with the new
1142 Gains are numbers between ``self.config.baseGain``
and 1.
1147 Relative weight to give the new solution when updating the model.
1148 A value of 1.0 gives equal weight to both solutions.
1153 If ``len(convergenceList) != len(gainList)+1``.
1155 nIter = len(convergenceList)
1156 if nIter != len(gainList) + 1:
1157 raise ValueError(
"convergenceList (%d) must be one element longer than gainList (%d)."
1158 % (len(convergenceList), len(gainList)))
1160 if self.config.baseGain
is None:
1163 baseGain = 1./(self.config.dcrNumSubfilters - 1)
1165 baseGain = self.config.baseGain
1167 if self.config.useProgressiveGain
and nIter > 2:
1175 estFinalConv = [((1 + gainList[i])*convergenceList[i + 1] - convergenceList[i])/gainList[i]
1176 for i
in range(nIter - 1)]
1179 estFinalConv = np.array(estFinalConv)
1180 estFinalConv[estFinalConv < 0] = 0
1182 estFinalConv = np.median(estFinalConv[max(nIter - 5, 0):])
1183 lastGain = gainList[-1]
1184 lastConv = convergenceList[-2]
1185 newConv = convergenceList[-1]
1190 predictedConv = (estFinalConv*lastGain + lastConv)/(1. + lastGain)
1196 delta = (predictedConv - newConv)/((lastConv - estFinalConv)/(1 + lastGain))
1197 newGain = 1 - abs(delta)
1199 newGain = (newGain + lastGain)/2.
1200 gain = max(baseGain, newGain)
1203 gainList.append(gain)
1207 """Build an array that smoothly tapers to 0 away from detected sources.
1211 dcrModels : `lsst.pipe.tasks.DcrModel`
1212 Best fit model of the true sky after correcting chromatic effects.
1213 dcrBBox : `lsst.geom.box.Box2I`
1214 Sub-region of the coadd which includes a buffer to allow for DCR.
1218 weights : `numpy.ndarray`
or `float`
1219 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
1220 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
1225 If ``useModelWeights``
is set
and ``modelWeightsWidth``
is negative.
1227 if not self.config.useModelWeights:
1229 if self.config.modelWeightsWidth < 0:
1230 raise ValueError(
"modelWeightsWidth must not be negative if useModelWeights is set")
1231 convergeMask = dcrModels.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
1232 convergeMaskPixels = dcrModels.mask[dcrBBox].array & convergeMask > 0
1233 weights = np.zeros_like(dcrModels[0][dcrBBox].array)
1234 weights[convergeMaskPixels] = 1.
1235 weights = ndimage.filters.gaussian_filter(weights, self.config.modelWeightsWidth)
1236 weights /= np.max(weights)
1240 """Smoothly replace model pixel values with those from a
1241 reference at locations away from detected sources.
1246 The new DCR model images
from the current iteration.
1247 The values will be modified
in place.
1249 A reference image used to supply the default pixel values.
1250 modelWeights : `numpy.ndarray`
or `float`
1251 A 2D array of weight values that tapers smoothly to zero away
from detected sources.
1252 Set to a placeholder value of 1.0
if ``self.config.useModelWeights``
is False.
1254 if self.config.useModelWeights:
1255 for model
in modelImages:
1256 model.array *= modelWeights
1257 model.array += refImage.array*(1. - modelWeights)/self.config.dcrNumSubfilters
1260 """Pre-load sub-regions of a list of exposures.
1264 bbox : `lsst.geom.box.Box2I`
1267 Statistics control object for coadd
1268 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
1269 `lsst.daf.persistence.ButlerDataRef`
1270 The data references to the input warped exposures.
1271 imageScalerList : `list` of `lsst.pipe.task.ImageScaler`
1272 The image scalars correct
for the zero point of the exposures.
1273 spanSetMaskList : `list` of `dict` containing spanSet lists,
or None
1274 Each element
is dict
with keys = mask plane name to add the spans to
1278 subExposures : `dict`
1279 The `dict` keys are the visit IDs,
1280 and the values are `lsst.afw.image.ExposureF`
1281 The pre-loaded exposures
for the current subregion.
1282 The variance plane contains weights,
and not the variance
1284 tempExpName = self.getTempExpDatasetName(self.warpType)
1285 zipIterables = zip(warpRefList, imageScalerList, spanSetMaskList)
1287 for warpExpRef, imageScaler, altMaskSpans
in zipIterables:
1288 if isinstance(warpExpRef, DeferredDatasetHandle):
1289 exposure = warpExpRef.get(parameters={
'bbox': bbox})
1291 exposure = warpExpRef.get(tempExpName +
"_sub", bbox=bbox)
1292 visit = warpExpRef.dataId[
"visit"]
1293 if altMaskSpans
is not None:
1294 self.applyAltMaskPlanes(exposure.mask, altMaskSpans)
1295 imageScaler.scaleMaskedImage(exposure.maskedImage)
1297 exposure.variance.array[:, :] = 0.
1299 exposure.variance.array[(exposure.mask.array & statsCtrl.getAndMask()) == 0] = 1.
1302 exposure.image.array[(exposure.mask.array & statsCtrl.getAndMask()) > 0] = 0.
1303 subExposures[visit] = exposure
1307 """Compute the PSF of the coadd from the exposures with the best seeing.
1311 templateCoadd : `lsst.afw.image.ExposureF`
1312 The initial coadd exposure before accounting for DCR.
1313 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle`
or
1314 `lsst.daf.persistence.ButlerDataRef`
1315 The data references to the input warped exposures.
1320 The average PSF of the input exposures
with the best seeing.
1322 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
1323 tempExpName = self.getTempExpDatasetName(self.warpType)
1326 ccds = templateCoadd.getInfo().getCoaddInputs().ccds
1327 psfRefSize = templateCoadd.getPsf().computeShape().getDeterminantRadius()*sigma2fwhm
1328 psfSizes = np.zeros(len(ccds))
1329 ccdVisits = np.array(ccds[
"visit"])
1330 for warpExpRef
in warpRefList:
1331 if isinstance(warpExpRef, DeferredDatasetHandle):
1333 psf = warpExpRef.get(component=
"psf")
1336 psf = warpExpRef.get(tempExpName).getPsf()
1337 visit = warpExpRef.dataId[
"visit"]
1338 psfSize = psf.computeShape().getDeterminantRadius()*sigma2fwhm
1339 psfSizes[ccdVisits == visit] = psfSize
1343 sizeThreshold = min(np.median(psfSizes), psfRefSize)
1344 goodPsfs = psfSizes <= sizeThreshold
1345 psf = measAlg.CoaddPsf(ccds[goodPsfs], templateCoadd.getWcs(),
1346 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)