25 from scipy
import ndimage
30 from lsst.daf.butler
import DeferredDatasetHandle
34 import lsst.pex.config
as pexConfig
37 from .assembleCoadd
import (AssembleCoaddTask,
38 CompareWarpAssembleCoaddConfig,
39 CompareWarpAssembleCoaddTask)
40 from .coaddBase
import makeSkyInfo
41 from .measurePsf
import MeasurePsfTask
43 __all__ = [
"DcrAssembleCoaddConnections",
"DcrAssembleCoaddTask",
"DcrAssembleCoaddConfig"]
47 dimensions=(
"tract",
"patch",
"abstract_filter",
"skymap"),
48 defaultTemplates={
"inputCoaddName":
"deep",
49 "outputCoaddName":
"dcr",
53 inputWarps = pipeBase.connectionTypes.Input(
54 doc=(
"Input list of warps to be assembled i.e. stacked."
55 "WarpType (e.g. direct, psfMatched) is controlled by the warpType config parameter"),
56 name=
"{inputCoaddName}Coadd_{warpType}Warp",
57 storageClass=
"ExposureF",
58 dimensions=(
"tract",
"patch",
"skymap",
"visit",
"instrument"),
62 skyMap = pipeBase.connectionTypes.Input(
63 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
64 name=
"{inputCoaddName}Coadd_skyMap",
65 storageClass=
"SkyMap",
66 dimensions=(
"skymap", ),
68 brightObjectMask = pipeBase.connectionTypes.PrerequisiteInput(
69 doc=(
"Input Bright Object Mask mask produced with external catalogs to be applied to the mask plane"
71 name=
"brightObjectMask",
72 storageClass=
"ObjectMaskCatalog",
73 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter"),
75 templateExposure = pipeBase.connectionTypes.Input(
76 doc=
"Input coadded exposure, produced by previous call to AssembleCoadd",
77 name=
"{fakesType}{inputCoaddName}Coadd{warpTypeSuffix}",
78 storageClass=
"ExposureF",
79 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter"),
81 dcrCoadds = pipeBase.connectionTypes.Output(
82 doc=
"Output coadded exposure, produced by stacking input warps",
83 name=
"{fakesType}{outputCoaddName}Coadd{warpTypeSuffix}",
84 storageClass=
"ExposureF",
85 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter",
"subfilter"),
88 dcrNImages = pipeBase.connectionTypes.Output(
89 doc=
"Output image of number of input images per pixel",
90 name=
"{outputCoaddName}Coadd_nImage",
91 storageClass=
"ImageU",
92 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter",
"subfilter"),
96 def __init__(self, *, config=None):
97 super().__init__(config=config)
98 if not config.doWrite:
99 self.outputs.remove(
"dcrCoadds")
103 pipelineConnections=DcrAssembleCoaddConnections):
104 dcrNumSubfilters = pexConfig.Field(
106 doc=
"Number of sub-filters to forward model chromatic effects to fit the supplied exposures.",
109 maxNumIter = pexConfig.Field(
112 doc=
"Maximum number of iterations of forward modeling.",
115 minNumIter = pexConfig.Field(
118 doc=
"Minimum number of iterations of forward modeling.",
121 convergenceThreshold = pexConfig.Field(
123 doc=
"Target relative change in convergence between iterations of forward modeling.",
126 useConvergence = pexConfig.Field(
128 doc=
"Use convergence test as a forward modeling end condition?"
129 "If not set, skips calculating convergence and runs for ``maxNumIter`` iterations",
132 baseGain = pexConfig.Field(
135 doc=
"Relative weight to give the new solution vs. the last solution when updating the model."
136 "A value of 1.0 gives equal weight to both solutions."
137 "Small values imply slower convergence of the solution, but can "
138 "help prevent overshooting and failures in the fit."
139 "If ``baseGain`` is None, a conservative gain "
140 "will be calculated from the number of subfilters. ",
143 useProgressiveGain = pexConfig.Field(
145 doc=
"Use a gain that slowly increases above ``baseGain`` to accelerate convergence? "
146 "When calculating the next gain, we use up to 5 previous gains and convergence values."
147 "Can be set to False to force the model to change at the rate of ``baseGain``. ",
150 doAirmassWeight = pexConfig.Field(
152 doc=
"Weight exposures by airmass? Useful if there are relatively few high-airmass observations.",
155 modelWeightsWidth = pexConfig.Field(
157 doc=
"Width of the region around detected sources to include in the DcrModel.",
160 useModelWeights = pexConfig.Field(
162 doc=
"Width of the region around detected sources to include in the DcrModel.",
165 splitSubfilters = pexConfig.Field(
167 doc=
"Calculate DCR for two evenly-spaced wavelengths in each subfilter."
168 "Instead of at the midpoint",
171 splitThreshold = pexConfig.Field(
173 doc=
"Minimum DCR difference within a subfilter to use ``splitSubfilters``, in pixels."
174 "Set to 0 to always split the subfilters.",
177 regularizeModelIterations = pexConfig.Field(
179 doc=
"Maximum relative change of the model allowed between iterations."
180 "Set to zero to disable.",
183 regularizeModelFrequency = pexConfig.Field(
185 doc=
"Maximum relative change of the model allowed between subfilters."
186 "Set to zero to disable.",
189 convergenceMaskPlanes = pexConfig.ListField(
191 default=[
"DETECTED"],
192 doc=
"Mask planes to use to calculate convergence."
194 regularizationWidth = pexConfig.Field(
197 doc=
"Minimum radius of a region to include in regularization, in pixels."
199 imageInterpOrder = pexConfig.Field(
201 doc=
"The order of the spline interpolation used to shift the image plane.",
204 accelerateModel = pexConfig.Field(
206 doc=
"Factor to amplify the differences between model planes by to speed convergence.",
209 doCalculatePsf = pexConfig.Field(
211 doc=
"Set to detect stars and recalculate the PSF from the final coadd."
212 "Otherwise the PSF is estimated from a selection of the best input exposures",
215 detectPsfSources = pexConfig.ConfigurableField(
216 target=measAlg.SourceDetectionTask,
217 doc=
"Task to detect sources for PSF measurement, if ``doCalculatePsf`` is set.",
219 measurePsfSources = pexConfig.ConfigurableField(
220 target=SingleFrameMeasurementTask,
221 doc=
"Task to measure sources for PSF measurement, if ``doCalculatePsf`` is set."
223 measurePsf = pexConfig.ConfigurableField(
224 target=MeasurePsfTask,
225 doc=
"Task to measure the PSF of the coadd, if ``doCalculatePsf`` is set.",
228 def setDefaults(self):
229 CompareWarpAssembleCoaddConfig.setDefaults(self)
230 self.assembleStaticSkyModel.retarget(CompareWarpAssembleCoaddTask)
232 self.assembleStaticSkyModel.warpType = self.warpType
234 self.assembleStaticSkyModel.doNImage =
False
235 self.assembleStaticSkyModel.doWrite =
False
236 self.detectPsfSources.returnOriginalFootprints =
False
237 self.detectPsfSources.thresholdPolarity =
"positive"
239 self.detectPsfSources.thresholdValue = 50
240 self.detectPsfSources.nSigmaToGrow = 2
242 self.detectPsfSources.minPixels = 25
244 self.detectPsfSources.thresholdType =
"pixel_stdev"
247 self.measurePsf.starSelector[
"objectSize"].doFluxLimit =
False
251 """Assemble DCR coadded images from a set of warps.
256 The number of pixels to grow each subregion by to allow for DCR.
260 As with AssembleCoaddTask, we want to assemble a coadded image from a set of
261 Warps (also called coadded temporary exposures), including the effects of
262 Differential Chromatic Refraction (DCR).
263 For full details of the mathematics and algorithm, please see
264 DMTN-037: DCR-matched template generation (https://dmtn-037.lsst.io).
266 This Task produces a DCR-corrected deepCoadd, as well as a dcrCoadd for
267 each subfilter used in the iterative calculation.
268 It begins by dividing the bandpass-defining filter into N equal bandwidth
269 "subfilters", and divides the flux in each pixel from an initial coadd
270 equally into each as a "dcrModel". Because the airmass and parallactic
271 angle of each individual exposure is known, we can calculate the shift
272 relative to the center of the band in each subfilter due to DCR. For each
273 exposure we apply this shift as a linear transformation to the dcrModels
274 and stack the results to produce a DCR-matched exposure. The matched
275 exposures are subtracted from the input exposures to produce a set of
276 residual images, and these residuals are reverse shifted for each
277 exposures' subfilters and stacked. The shifted and stacked residuals are
278 added to the dcrModels to produce a new estimate of the flux in each pixel
279 within each subfilter. The dcrModels are solved for iteratively, which
280 continues until the solution from a new iteration improves by less than
281 a set percentage, or a maximum number of iterations is reached.
282 Two forms of regularization are employed to reduce unphysical results.
283 First, the new solution is averaged with the solution from the previous
284 iteration, which mitigates oscillating solutions where the model
285 overshoots with alternating very high and low values.
286 Second, a common degeneracy when the data have a limited range of airmass or
287 parallactic angle values is for one subfilter to be fit with very low or
288 negative values, while another subfilter is fit with very high values. This
289 typically appears in the form of holes next to sources in one subfilter,
290 and corresponding extended wings in another. Because each subfilter has
291 a narrow bandwidth we assume that physical sources that are above the noise
292 level will not vary in flux by more than a factor of `frequencyClampFactor`
293 between subfilters, and pixels that have flux deviations larger than that
294 factor will have the excess flux distributed evenly among all subfilters.
295 If `splitSubfilters` is set, then each subfilter will be further sub-
296 divided during the forward modeling step (only). This approximates using
297 a higher number of subfilters that may be necessary for high airmass
298 observations, but does not increase the number of free parameters in the
299 fit. This is needed when there are high airmass observations which would
300 otherwise have significant DCR even within a subfilter. Because calculating
301 the shifted images takes most of the time, splitting the subfilters is
302 turned off by way of the `splitThreshold` option for low-airmass
303 observations that do not suffer from DCR within a subfilter.
306 ConfigClass = DcrAssembleCoaddConfig
307 _DefaultName =
"dcrAssembleCoadd"
309 def __init__(self, *args, **kwargs):
310 super().__init__(*args, **kwargs)
311 if self.config.doCalculatePsf:
312 self.schema = afwTable.SourceTable.makeMinimalSchema()
313 self.makeSubtask(
"detectPsfSources", schema=self.schema)
314 self.makeSubtask(
"measurePsfSources", schema=self.schema)
315 self.makeSubtask(
"measurePsf", schema=self.schema)
317 @utils.inheritDoc(pipeBase.PipelineTask)
318 def runQuantum(self, butlerQC, inputRefs, outputRefs):
323 Assemble a coadd from a set of Warps.
325 PipelineTask (Gen3) entry point to Coadd a set of Warps.
326 Analogous to `runDataRef`, it prepares all the data products to be
327 passed to `run`, and processes the results before returning a struct
328 of results to be written out. AssembleCoadd cannot fit all Warps in memory.
329 Therefore, its inputs are accessed subregion by subregion
330 by the Gen3 `DeferredDatasetHandle` that is analagous to the Gen2
331 `lsst.daf.persistence.ButlerDataRef`. Any updates to this method should
332 correspond to an update in `runDataRef` while both entry points
335 inputData = butlerQC.get(inputRefs)
339 skyMap = inputData[
"skyMap"]
340 outputDataId = butlerQC.quantum.dataId
343 tractId=outputDataId[
'tract'],
344 patchId=outputDataId[
'patch'])
348 warpRefList = inputData[
'inputWarps']
350 inputs = self.prepareInputs(warpRefList)
351 self.log.info(
"Found %d %s", len(inputs.tempExpRefList),
352 self.getTempExpDatasetName(self.warpType))
353 if len(inputs.tempExpRefList) == 0:
354 self.log.warn(
"No coadd temporary exposures found")
357 supplementaryData = self.makeSupplementaryDataGen3(butlerQC, inputRefs, outputRefs)
358 retStruct = self.run(inputData[
'skyInfo'], inputs.tempExpRefList, inputs.imageScalerList,
359 inputs.weightList, supplementaryData=supplementaryData)
361 inputData.setdefault(
'brightObjectMask',
None)
362 for subfilter
in range(self.config.dcrNumSubfilters):
364 retStruct.dcrCoadds[subfilter].setPsf(retStruct.coaddExposure.getPsf())
365 self.processResults(retStruct.dcrCoadds[subfilter], inputData[
'brightObjectMask'], outputDataId)
367 if self.config.doWrite:
368 butlerQC.put(retStruct, outputRefs)
372 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
373 """Assemble a coadd from a set of warps.
375 Coadd a set of Warps. Compute weights to be applied to each Warp and
376 find scalings to match the photometric zeropoint to a reference Warp.
377 Assemble the Warps using run method.
378 Forward model chromatic effects across multiple subfilters,
379 and subtract from the input Warps to build sets of residuals.
380 Use the residuals to construct a new ``DcrModel`` for each subfilter,
381 and iterate until the model converges.
382 Interpolate over NaNs and optionally write the coadd to disk.
383 Return the coadded exposure.
387 dataRef : `lsst.daf.persistence.ButlerDataRef`
388 Data reference defining the patch for coaddition and the
390 selectDataList : `list` of `lsst.daf.persistence.ButlerDataRef`
391 List of data references to warps. Data to be coadded will be
392 selected from this list based on overlap with the patch defined by
397 results : `lsst.pipe.base.Struct`
398 The Struct contains the following fields:
400 - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`)
401 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`)
402 - ``dcrCoadds``: `list` of coadded exposures for each subfilter
403 - ``dcrNImages``: `list` of exposure count images for each subfilter
405 if (selectDataList
is None and warpRefList
is None)
or (selectDataList
and warpRefList):
406 raise RuntimeError(
"runDataRef must be supplied either a selectDataList or warpRefList")
408 skyInfo = self.getSkyInfo(dataRef)
409 if warpRefList
is None:
410 calExpRefList = self.selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
411 if len(calExpRefList) == 0:
412 self.log.warn(
"No exposures to coadd")
414 self.log.info(
"Coadding %d exposures", len(calExpRefList))
416 warpRefList = self.getTempExpRefList(dataRef, calExpRefList)
418 inputData = self.prepareInputs(warpRefList)
419 self.log.info(
"Found %d %s", len(inputData.tempExpRefList),
420 self.getTempExpDatasetName(self.warpType))
421 if len(inputData.tempExpRefList) == 0:
422 self.log.warn(
"No coadd temporary exposures found")
425 supplementaryData = self.makeSupplementaryData(dataRef, warpRefList=inputData.tempExpRefList)
427 results = self.run(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
428 inputData.weightList, supplementaryData=supplementaryData)
430 self.log.warn(
"Could not construct DcrModel for patch %s: no data to coadd.",
431 skyInfo.patchInfo.getIndex())
434 if self.config.doCalculatePsf:
435 self.measureCoaddPsf(results.coaddExposure)
436 brightObjects = self.readBrightObjectMasks(dataRef)
if self.config.doMaskBrightObjects
else None
437 for subfilter
in range(self.config.dcrNumSubfilters):
439 results.dcrCoadds[subfilter].setPsf(results.coaddExposure.getPsf())
440 self.processResults(results.dcrCoadds[subfilter],
441 brightObjectMasks=brightObjects, dataId=dataRef.dataId)
442 if self.config.doWrite:
443 self.log.info(
"Persisting dcrCoadd")
444 dataRef.put(results.dcrCoadds[subfilter],
"dcrCoadd", subfilter=subfilter,
445 numSubfilters=self.config.dcrNumSubfilters)
446 if self.config.doNImage
and results.dcrNImages
is not None:
447 dataRef.put(results.dcrNImages[subfilter],
"dcrCoadd_nImage", subfilter=subfilter,
448 numSubfilters=self.config.dcrNumSubfilters)
452 @utils.inheritDoc(AssembleCoaddTask)
454 """Load the previously-generated template coadd.
456 This can be removed entirely once we no longer support the Gen 2 butler.
460 templateCoadd : `lsst.pipe.base.Struct`
461 Result struct with components:
463 - ``templateCoadd``: coadded exposure (`lsst.afw.image.ExposureF`)
465 templateCoadd = butlerQC.get(inputRefs.templateExposure)
467 return pipeBase.Struct(templateCoadd=templateCoadd)
469 def measureCoaddPsf(self, coaddExposure):
470 """Detect sources on the coadd exposure and measure the final PSF.
474 coaddExposure : `lsst.afw.image.Exposure`
475 The final coadded exposure.
477 table = afwTable.SourceTable.make(self.schema)
478 detResults = self.detectPsfSources.
run(table, coaddExposure, clearMask=
False)
479 coaddSources = detResults.sources
480 self.measurePsfSources.
run(
481 measCat=coaddSources,
482 exposure=coaddExposure
489 psfResults = self.measurePsf.
run(coaddExposure, coaddSources)
490 except Exception
as e:
491 self.log.warn(
"Unable to calculate PSF, using default coadd PSF: %s" % e)
493 coaddExposure.setPsf(psfResults.psf)
495 def prepareDcrInputs(self, templateCoadd, warpRefList, weightList):
496 """Prepare the DCR coadd by iterating through the visitInfo of the input warps.
498 Sets the property ``bufferSize``.
502 templateCoadd : `lsst.afw.image.ExposureF`
503 The initial coadd exposure before accounting for DCR.
504 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
505 `lsst.daf.persistence.ButlerDataRef`
506 The data references to the input warped exposures.
507 weightList : `list` of `float`
508 The weight to give each input exposure in the coadd
509 Will be modified in place if ``doAirmassWeight`` is set.
513 dcrModels : `lsst.pipe.tasks.DcrModel`
514 Best fit model of the true sky after correcting chromatic effects.
519 If ``lambdaMin`` is missing from the Mapper class of the obs package being used.
521 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
522 filterInfo = templateCoadd.getFilter()
523 if np.isnan(filterInfo.getFilterProperty().getLambdaMin()):
524 raise NotImplementedError(
"No minimum/maximum wavelength information found"
525 " in the filter definition! Please add lambdaMin and lambdaMax"
526 " to the Mapper class in your obs package.")
527 tempExpName = self.getTempExpDatasetName(self.warpType)
532 for visitNum, warpExpRef
in enumerate(warpRefList):
533 if isinstance(warpExpRef, DeferredDatasetHandle):
535 visitInfo = warpExpRef.get(component=
"visitInfo")
536 psf = warpExpRef.get(component=
"psf")
539 visitInfo = warpExpRef.get(tempExpName +
"_visitInfo")
540 psf = warpExpRef.get(tempExpName).getPsf()
541 visit = warpExpRef.dataId[
"visit"]
542 psfSize = psf.computeShape().getDeterminantRadius()*sigma2fwhm
543 airmass = visitInfo.getBoresightAirmass()
544 parallacticAngle = visitInfo.getBoresightParAngle().asDegrees()
545 airmassDict[visit] = airmass
546 angleDict[visit] = parallacticAngle
547 psfSizeDict[visit] = psfSize
548 if self.config.doAirmassWeight:
549 weightList[visitNum] *= airmass
550 dcrShifts.append(np.max(np.abs(calculateDcr(visitInfo, templateCoadd.getWcs(),
551 filterInfo, self.config.dcrNumSubfilters))))
552 self.log.info(
"Selected airmasses:\n%s", airmassDict)
553 self.log.info(
"Selected parallactic angles:\n%s", angleDict)
554 self.log.info(
"Selected PSF sizes:\n%s", psfSizeDict)
555 self.bufferSize = int(np.ceil(np.max(dcrShifts)) + 1)
557 psf = self.selectCoaddPsf(templateCoadd, warpRefList)
558 except Exception
as e:
559 self.log.warn(
"Unable to calculate restricted PSF, using default coadd PSF: %s" % e)
561 psf = templateCoadd.getPsf()
562 dcrModels = DcrModel.fromImage(templateCoadd.maskedImage,
563 self.config.dcrNumSubfilters,
564 filterInfo=filterInfo,
569 def run(self, skyInfo, warpRefList, imageScalerList, weightList,
570 supplementaryData=None):
571 """Assemble the coadd.
573 Requires additional inputs Struct ``supplementaryData`` to contain a
574 ``templateCoadd`` that serves as the model of the static sky.
576 Find artifacts and apply them to the warps' masks creating a list of
577 alternative masks with a new "CLIPPED" plane and updated "NO_DATA" plane
578 Then pass these alternative masks to the base class's assemble method.
580 Divide the ``templateCoadd`` evenly between each subfilter of a
581 ``DcrModel`` as the starting best estimate of the true wavelength-
582 dependent sky. Forward model the ``DcrModel`` using the known
583 chromatic effects in each subfilter and calculate a convergence metric
584 based on how well the modeled template matches the input warps. If
585 the convergence has not yet reached the desired threshold, then shift
586 and stack the residual images to build a new ``DcrModel``. Apply
587 conditioning to prevent oscillating solutions between iterations or
590 Once the ``DcrModel`` reaches convergence or the maximum number of
591 iterations has been reached, fill the metadata for each subfilter
592 image and make them proper ``coaddExposure``s.
596 skyInfo : `lsst.pipe.base.Struct`
597 Patch geometry information, from getSkyInfo
598 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
599 `lsst.daf.persistence.ButlerDataRef`
600 The data references to the input warped exposures.
601 imageScalerList : `list` of `lsst.pipe.task.ImageScaler`
602 The image scalars correct for the zero point of the exposures.
603 weightList : `list` of `float`
604 The weight to give each input exposure in the coadd
605 supplementaryData : `lsst.pipe.base.Struct`
606 Result struct returned by ``makeSupplementaryData`` with components:
608 - ``templateCoadd``: coadded exposure (`lsst.afw.image.Exposure`)
612 result : `lsst.pipe.base.Struct`
613 Result struct with components:
615 - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`)
616 - ``nImage``: exposure count image (`lsst.afw.image.ImageU`)
617 - ``dcrCoadds``: `list` of coadded exposures for each subfilter
618 - ``dcrNImages``: `list` of exposure count images for each subfilter
620 minNumIter = self.config.minNumIter
or self.config.dcrNumSubfilters
621 maxNumIter = self.config.maxNumIter
or self.config.dcrNumSubfilters*3
622 templateCoadd = supplementaryData.templateCoadd
623 baseMask = templateCoadd.mask.clone()
626 baseVariance = templateCoadd.variance.clone()
627 baseVariance /= self.config.dcrNumSubfilters
628 spanSetMaskList = self.findArtifacts(templateCoadd, warpRefList, imageScalerList)
630 templateCoadd.setMask(baseMask)
631 badMaskPlanes = self.config.badMaskPlanes[:]
636 badPixelMask = templateCoadd.mask.getPlaneBitMask(badMaskPlanes)
638 stats = self.prepareStats(mask=badPixelMask)
639 dcrModels = self.prepareDcrInputs(templateCoadd, warpRefList, weightList)
640 if self.config.doNImage:
641 dcrNImages, dcrWeights = self.calculateNImage(dcrModels, skyInfo.bbox, warpRefList,
642 spanSetMaskList, stats.ctrl)
643 nImage = afwImage.ImageU(skyInfo.bbox)
647 for dcrNImage
in dcrNImages:
653 nSubregions = (ceil(skyInfo.bbox.getHeight()/subregionSize[1])
654 * ceil(skyInfo.bbox.getWidth()/subregionSize[0]))
656 for subBBox
in self._subBBoxIter(skyInfo.bbox, subregionSize):
659 self.log.info(
"Computing coadd over patch %s subregion %s of %s: %s",
660 skyInfo.patchInfo.getIndex(), subIter, nSubregions, subBBox)
662 dcrBBox.grow(self.bufferSize)
663 dcrBBox.clip(dcrModels.bbox)
664 modelWeights = self.calculateModelWeights(dcrModels, dcrBBox)
665 subExposures = self.loadSubExposures(dcrBBox, stats.ctrl, warpRefList,
666 imageScalerList, spanSetMaskList)
667 convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
668 warpRefList, weightList, stats.ctrl)
669 self.log.info(
"Initial convergence : %s", convergenceMetric)
670 convergenceList = [convergenceMetric]
672 convergenceCheck = 1.
673 refImage = templateCoadd.image
674 while (convergenceCheck > self.config.convergenceThreshold
or modelIter <= minNumIter):
675 gain = self.calculateGain(convergenceList, gainList)
676 self.dcrAssembleSubregion(dcrModels, subExposures, subBBox, dcrBBox, warpRefList,
677 stats.ctrl, convergenceMetric, gain,
678 modelWeights, refImage, dcrWeights)
679 if self.config.useConvergence:
680 convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
681 warpRefList, weightList, stats.ctrl)
682 if convergenceMetric == 0:
683 self.log.warn(
"Coadd patch %s subregion %s had convergence metric of 0.0 which is "
684 "most likely due to there being no valid data in the region.",
685 skyInfo.patchInfo.getIndex(), subIter)
687 convergenceCheck = (convergenceList[-1] - convergenceMetric)/convergenceMetric
688 if (convergenceCheck < 0) & (modelIter > minNumIter):
689 self.log.warn(
"Coadd patch %s subregion %s diverged before reaching maximum "
690 "iterations or desired convergence improvement of %s."
692 skyInfo.patchInfo.getIndex(), subIter,
693 self.config.convergenceThreshold, convergenceCheck)
695 convergenceList.append(convergenceMetric)
696 if modelIter > maxNumIter:
697 if self.config.useConvergence:
698 self.log.warn(
"Coadd patch %s subregion %s reached maximum iterations "
699 "before reaching desired convergence improvement of %s."
700 " Final convergence improvement: %s",
701 skyInfo.patchInfo.getIndex(), subIter,
702 self.config.convergenceThreshold, convergenceCheck)
705 if self.config.useConvergence:
706 self.log.info(
"Iteration %s with convergence metric %s, %.4f%% improvement (gain: %.2f)",
707 modelIter, convergenceMetric, 100.*convergenceCheck, gain)
710 if self.config.useConvergence:
711 self.log.info(
"Coadd patch %s subregion %s finished with "
712 "convergence metric %s after %s iterations",
713 skyInfo.patchInfo.getIndex(), subIter, convergenceMetric, modelIter)
715 self.log.info(
"Coadd patch %s subregion %s finished after %s iterations",
716 skyInfo.patchInfo.getIndex(), subIter, modelIter)
717 if self.config.useConvergence
and convergenceMetric > 0:
718 self.log.info(
"Final convergence improvement was %.4f%% overall",
719 100*(convergenceList[0] - convergenceMetric)/convergenceMetric)
721 dcrCoadds = self.fillCoadd(dcrModels, skyInfo, warpRefList, weightList,
722 calibration=self.scaleZeroPoint.getPhotoCalib(),
723 coaddInputs=templateCoadd.getInfo().getCoaddInputs(),
725 variance=baseVariance)
726 coaddExposure = self.stackCoadd(dcrCoadds)
727 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
728 dcrCoadds=dcrCoadds, dcrNImages=dcrNImages)
730 def calculateNImage(self, dcrModels, bbox, warpRefList, spanSetMaskList, statsCtrl):
731 """Calculate the number of exposures contributing to each subfilter.
735 dcrModels : `lsst.pipe.tasks.DcrModel`
736 Best fit model of the true sky after correcting chromatic effects.
737 bbox : `lsst.geom.box.Box2I`
738 Bounding box of the patch to coadd.
739 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
740 `lsst.daf.persistence.ButlerDataRef`
741 The data references to the input warped exposures.
742 spanSetMaskList : `list` of `dict` containing spanSet lists, or None
743 Each element of the `dict` contains the new mask plane name
744 (e.g. "CLIPPED and/or "NO_DATA") as the key,
745 and the list of SpanSets to apply to the mask.
746 statsCtrl : `lsst.afw.math.StatisticsControl`
747 Statistics control object for coadd
751 dcrNImages : `list` of `lsst.afw.image.ImageU`
752 List of exposure count images for each subfilter
753 dcrWeights : `list` of `lsst.afw.image.ImageF`
754 Per-pixel weights for each subfilter.
755 Equal to 1/(number of unmasked images contributing to each pixel).
757 dcrNImages = [afwImage.ImageU(bbox)
for subfilter
in range(self.config.dcrNumSubfilters)]
758 dcrWeights = [afwImage.ImageF(bbox)
for subfilter
in range(self.config.dcrNumSubfilters)]
759 tempExpName = self.getTempExpDatasetName(self.warpType)
760 for warpExpRef, altMaskSpans
in zip(warpRefList, spanSetMaskList):
761 if isinstance(warpExpRef, DeferredDatasetHandle):
763 exposure = warpExpRef.get(parameters={
'bbox': bbox})
766 exposure = warpExpRef.get(tempExpName +
"_sub", bbox=bbox)
767 visitInfo = exposure.getInfo().getVisitInfo()
768 wcs = exposure.getInfo().getWcs()
770 if altMaskSpans
is not None:
771 self.applyAltMaskPlanes(mask, altMaskSpans)
772 weightImage = np.zeros_like(exposure.image.array)
773 weightImage[(mask.array & statsCtrl.getAndMask()) == 0] = 1.
776 weightsGenerator = self.dcrResiduals(weightImage, visitInfo, wcs, dcrModels.filter)
777 for shiftedWeights, dcrNImage, dcrWeight
in zip(weightsGenerator, dcrNImages, dcrWeights):
778 dcrNImage.array += np.rint(shiftedWeights).astype(dcrNImage.array.dtype)
779 dcrWeight.array += shiftedWeights
781 weightsThreshold = 1.
782 goodPix = dcrWeights[0].array > weightsThreshold
783 for weights
in dcrWeights[1:]:
784 goodPix = (weights.array > weightsThreshold) & goodPix
785 for subfilter
in range(self.config.dcrNumSubfilters):
786 dcrWeights[subfilter].array[goodPix] = 1./dcrWeights[subfilter].array[goodPix]
787 dcrWeights[subfilter].array[~goodPix] = 0.
788 dcrNImages[subfilter].array[~goodPix] = 0
789 return (dcrNImages, dcrWeights)
792 statsCtrl, convergenceMetric,
793 gain, modelWeights, refImage, dcrWeights):
794 """Assemble the DCR coadd for a sub-region.
796 Build a DCR-matched template for each input exposure, then shift the
797 residuals according to the DCR in each subfilter.
798 Stack the shifted residuals and apply them as a correction to the
799 solution from the previous iteration.
800 Restrict the new model solutions from varying by more than a factor of
801 `modelClampFactor` from the last solution, and additionally restrict the
802 individual subfilter models from varying by more than a factor of
803 `frequencyClampFactor` from their average.
804 Finally, mitigate potentially oscillating solutions by averaging the new
805 solution with the solution from the previous iteration, weighted by
806 their convergence metric.
810 dcrModels : `lsst.pipe.tasks.DcrModel`
811 Best fit model of the true sky after correcting chromatic effects.
812 subExposures : `dict` of `lsst.afw.image.ExposureF`
813 The pre-loaded exposures for the current subregion.
814 bbox : `lsst.geom.box.Box2I`
815 Bounding box of the subregion to coadd.
816 dcrBBox : `lsst.geom.box.Box2I`
817 Sub-region of the coadd which includes a buffer to allow for DCR.
818 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
819 `lsst.daf.persistence.ButlerDataRef`
820 The data references to the input warped exposures.
821 statsCtrl : `lsst.afw.math.StatisticsControl`
822 Statistics control object for coadd
823 convergenceMetric : `float`
824 Quality of fit metric for the matched templates of the input images.
825 gain : `float`, optional
826 Relative weight to give the new solution when updating the model.
827 modelWeights : `numpy.ndarray` or `float`
828 A 2D array of weight values that tapers smoothly to zero away from detected sources.
829 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False.
830 refImage : `lsst.afw.image.Image`
831 A reference image used to supply the default pixel values.
832 dcrWeights : `list` of `lsst.afw.image.Image`
833 Per-pixel weights for each subfilter.
834 Equal to 1/(number of unmasked images contributing to each pixel).
836 residualGeneratorList = []
838 for warpExpRef
in warpRefList:
839 visit = warpExpRef.dataId[
"visit"]
840 exposure = subExposures[visit]
841 visitInfo = exposure.getInfo().getVisitInfo()
842 wcs = exposure.getInfo().getWcs()
843 templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
844 order=self.config.imageInterpOrder,
845 splitSubfilters=self.config.splitSubfilters,
846 splitThreshold=self.config.splitThreshold,
847 amplifyModel=self.config.accelerateModel)
848 residual = exposure.image.array - templateImage.array
850 residual *= exposure.variance.array
854 residualGeneratorList.append(self.dcrResiduals(residual, visitInfo, wcs, dcrModels.filter))
856 dcrSubModelOut = self.newModelFromResidual(dcrModels, residualGeneratorList, dcrBBox, statsCtrl,
858 modelWeights=modelWeights,
860 dcrWeights=dcrWeights)
861 dcrModels.assign(dcrSubModelOut, bbox)
864 """Prepare a residual image for stacking in each subfilter by applying the reverse DCR shifts.
868 residual : `numpy.ndarray`
869 The residual masked image for one exposure,
870 after subtracting the matched template
871 visitInfo : `lsst.afw.image.VisitInfo`
872 Metadata for the exposure.
873 wcs : `lsst.afw.geom.SkyWcs`
874 Coordinate system definition (wcs) for the exposure.
875 filterInfo : `lsst.afw.image.Filter`
876 The filter definition, set in the current instruments' obs package.
877 Required for any calculation of DCR, including making matched templates.
881 residualImage : `numpy.ndarray`
882 The residual image for the next subfilter, shifted for DCR.
886 filteredResidual = ndimage.spline_filter(residual, order=self.config.imageInterpOrder)
889 dcrShift = calculateDcr(visitInfo, wcs, filterInfo, self.config.dcrNumSubfilters,
890 splitSubfilters=
False)
892 yield applyDcr(filteredResidual, dcr, useInverse=
True, splitSubfilters=
False,
893 doPrefilter=
False, order=self.config.imageInterpOrder)
896 gain, modelWeights, refImage, dcrWeights):
897 """Calculate a new DcrModel from a set of image residuals.
901 dcrModels : `lsst.pipe.tasks.DcrModel`
902 Current model of the true sky after correcting chromatic effects.
903 residualGeneratorList : `generator` of `numpy.ndarray`
904 The residual image for the next subfilter, shifted for DCR.
905 dcrBBox : `lsst.geom.box.Box2I`
906 Sub-region of the coadd which includes a buffer to allow for DCR.
907 statsCtrl : `lsst.afw.math.StatisticsControl`
908 Statistics control object for coadd
910 Relative weight to give the new solution when updating the model.
911 modelWeights : `numpy.ndarray` or `float`
912 A 2D array of weight values that tapers smoothly to zero away from detected sources.
913 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False.
914 refImage : `lsst.afw.image.Image`
915 A reference image used to supply the default pixel values.
916 dcrWeights : `list` of `lsst.afw.image.Image`
917 Per-pixel weights for each subfilter.
918 Equal to 1/(number of unmasked images contributing to each pixel).
922 dcrModel : `lsst.pipe.tasks.DcrModel`
923 New model of the true sky after correcting chromatic effects.
926 for subfilter, model
in enumerate(dcrModels):
927 residualsList = [next(residualGenerator)
for residualGenerator
in residualGeneratorList]
928 residual = np.sum(residualsList, axis=0)
929 residual *= dcrWeights[subfilter][dcrBBox].array
931 newModel = model[dcrBBox].clone()
932 newModel.array += residual
934 badPixels = ~np.isfinite(newModel.array)
935 newModel.array[badPixels] = model[dcrBBox].array[badPixels]
936 if self.config.regularizeModelIterations > 0:
937 dcrModels.regularizeModelIter(subfilter, newModel, dcrBBox,
938 self.config.regularizeModelIterations,
939 self.config.regularizationWidth)
940 newModelImages.append(newModel)
941 if self.config.regularizeModelFrequency > 0:
942 dcrModels.regularizeModelFreq(newModelImages, dcrBBox, statsCtrl,
943 self.config.regularizeModelFrequency,
944 self.config.regularizationWidth)
945 dcrModels.conditionDcrModel(newModelImages, dcrBBox, gain=gain)
946 self.applyModelWeights(newModelImages, refImage[dcrBBox], modelWeights)
947 return DcrModel(newModelImages, dcrModels.filter, dcrModels.psf,
948 dcrModels.mask, dcrModels.variance)
951 """Calculate a quality of fit metric for the matched templates.
955 dcrModels : `lsst.pipe.tasks.DcrModel`
956 Best fit model of the true sky after correcting chromatic effects.
957 subExposures : `dict` of `lsst.afw.image.ExposureF`
958 The pre-loaded exposures for the current subregion.
959 bbox : `lsst.geom.box.Box2I`
961 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
962 `lsst.daf.persistence.ButlerDataRef`
963 The data references to the input warped exposures.
964 weightList : `list` of `float`
965 The weight to give each input exposure in the coadd
966 statsCtrl : `lsst.afw.math.StatisticsControl`
967 Statistics control object for coadd
971 convergenceMetric : `float`
972 Quality of fit metric for all input exposures, within the sub-region
974 significanceImage = np.abs(dcrModels.getReferenceImage(bbox))
976 significanceImage += nSigma*dcrModels.calculateNoiseCutoff(dcrModels[1], statsCtrl,
977 bufferSize=self.bufferSize)
978 if np.max(significanceImage) == 0:
979 significanceImage += 1.
983 for warpExpRef, expWeight
in zip(warpRefList, weightList):
984 visit = warpExpRef.dataId[
"visit"]
985 exposure = subExposures[visit][bbox]
986 singleMetric = self.calculateSingleConvergence(dcrModels, exposure, significanceImage, statsCtrl)
987 metric += singleMetric
988 metricList[visit] = singleMetric
990 self.log.info(
"Individual metrics:\n%s", metricList)
991 return 1.0
if weight == 0.0
else metric/weight
994 """Calculate a quality of fit metric for a single matched template.
998 dcrModels : `lsst.pipe.tasks.DcrModel`
999 Best fit model of the true sky after correcting chromatic effects.
1000 exposure : `lsst.afw.image.ExposureF`
1001 The input warped exposure to evaluate.
1002 significanceImage : `numpy.ndarray`
1003 Array of weights for each pixel corresponding to its significance
1004 for the convergence calculation.
1005 statsCtrl : `lsst.afw.math.StatisticsControl`
1006 Statistics control object for coadd
1010 convergenceMetric : `float`
1011 Quality of fit metric for one exposure, within the sub-region.
1013 convergeMask = exposure.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
1014 templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
1015 order=self.config.imageInterpOrder,
1016 splitSubfilters=self.config.splitSubfilters,
1017 splitThreshold=self.config.splitThreshold,
1018 amplifyModel=self.config.accelerateModel)
1019 diffVals = np.abs(exposure.image.array - templateImage.array)*significanceImage
1020 refVals = np.abs(exposure.image.array + templateImage.array)*significanceImage/2.
1022 finitePixels = np.isfinite(diffVals)
1023 goodMaskPixels = (exposure.mask.array & statsCtrl.getAndMask()) == 0
1024 convergeMaskPixels = exposure.mask.array & convergeMask > 0
1025 usePixels = finitePixels & goodMaskPixels & convergeMaskPixels
1026 if np.sum(usePixels) == 0:
1029 diffUse = diffVals[usePixels]
1030 refUse = refVals[usePixels]
1031 metric = np.sum(diffUse/np.median(diffUse))/np.sum(refUse/np.median(diffUse))
1035 """Add a list of sub-band coadds together.
1039 dcrCoadds : `list` of `lsst.afw.image.ExposureF`
1040 A list of coadd exposures, each exposure containing
1041 the model for one subfilter.
1045 coaddExposure : `lsst.afw.image.ExposureF`
1046 A single coadd exposure that is the sum of the sub-bands.
1048 coaddExposure = dcrCoadds[0].clone()
1049 for coadd
in dcrCoadds[1:]:
1050 coaddExposure.maskedImage += coadd.maskedImage
1051 return coaddExposure
1053 def fillCoadd(self, dcrModels, skyInfo, warpRefList, weightList, calibration=None, coaddInputs=None,
1054 mask=None, variance=None):
1055 """Create a list of coadd exposures from a list of masked images.
1059 dcrModels : `lsst.pipe.tasks.DcrModel`
1060 Best fit model of the true sky after correcting chromatic effects.
1061 skyInfo : `lsst.pipe.base.Struct`
1062 Patch geometry information, from getSkyInfo
1063 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
1064 `lsst.daf.persistence.ButlerDataRef`
1065 The data references to the input warped exposures.
1066 weightList : `list` of `float`
1067 The weight to give each input exposure in the coadd
1068 calibration : `lsst.afw.Image.PhotoCalib`, optional
1069 Scale factor to set the photometric calibration of an exposure.
1070 coaddInputs : `lsst.afw.Image.CoaddInputs`, optional
1071 A record of the observations that are included in the coadd.
1072 mask : `lsst.afw.image.Mask`, optional
1073 Optional mask to override the values in the final coadd.
1074 variance : `lsst.afw.image.Image`, optional
1075 Optional variance plane to override the values in the final coadd.
1079 dcrCoadds : `list` of `lsst.afw.image.ExposureF`
1080 A list of coadd exposures, each exposure containing
1081 the model for one subfilter.
1084 refModel = dcrModels.getReferenceImage()
1085 for model
in dcrModels:
1086 if self.config.accelerateModel > 1:
1087 model.array = (model.array - refModel)*self.config.accelerateModel + refModel
1088 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
1089 if calibration
is not None:
1090 coaddExposure.setPhotoCalib(calibration)
1091 if coaddInputs
is not None:
1092 coaddExposure.getInfo().setCoaddInputs(coaddInputs)
1094 self.assembleMetadata(coaddExposure, warpRefList, weightList)
1096 coaddExposure.setPsf(dcrModels.psf)
1097 coaddUtils.setCoaddEdgeBits(dcrModels.mask[skyInfo.bbox], dcrModels.variance[skyInfo.bbox])
1098 maskedImage = afwImage.MaskedImageF(dcrModels.bbox)
1099 maskedImage.image = model
1100 maskedImage.mask = dcrModels.mask
1101 maskedImage.variance = dcrModels.variance
1102 coaddExposure.setMaskedImage(maskedImage[skyInfo.bbox])
1103 coaddExposure.setPhotoCalib(self.scaleZeroPoint.getPhotoCalib())
1104 if mask
is not None:
1105 coaddExposure.setMask(mask)
1106 if variance
is not None:
1107 coaddExposure.setVariance(variance)
1108 dcrCoadds.append(coaddExposure)
1112 """Calculate the gain to use for the current iteration.
1114 After calculating a new DcrModel, each value is averaged with the
1115 value in the corresponding pixel from the previous iteration. This
1116 reduces oscillating solutions that iterative techniques are plagued by,
1117 and speeds convergence. By far the biggest changes to the model
1118 happen in the first couple iterations, so we can also use a more
1119 aggressive gain later when the model is changing slowly.
1123 convergenceList : `list` of `float`
1124 The quality of fit metric from each previous iteration.
1125 gainList : `list` of `float`
1126 The gains used in each previous iteration: appended with the new
1128 Gains are numbers between ``self.config.baseGain`` and 1.
1133 Relative weight to give the new solution when updating the model.
1134 A value of 1.0 gives equal weight to both solutions.
1139 If ``len(convergenceList) != len(gainList)+1``.
1141 nIter = len(convergenceList)
1142 if nIter != len(gainList) + 1:
1143 raise ValueError(
"convergenceList (%d) must be one element longer than gainList (%d)."
1144 % (len(convergenceList), len(gainList)))
1146 if self.config.baseGain
is None:
1149 baseGain = 1./(self.config.dcrNumSubfilters - 1)
1151 baseGain = self.config.baseGain
1153 if self.config.useProgressiveGain
and nIter > 2:
1161 estFinalConv = [((1 + gainList[i])*convergenceList[i + 1] - convergenceList[i])/gainList[i]
1162 for i
in range(nIter - 1)]
1165 estFinalConv = np.array(estFinalConv)
1166 estFinalConv[estFinalConv < 0] = 0
1168 estFinalConv = np.median(estFinalConv[max(nIter - 5, 0):])
1169 lastGain = gainList[-1]
1170 lastConv = convergenceList[-2]
1171 newConv = convergenceList[-1]
1176 predictedConv = (estFinalConv*lastGain + lastConv)/(1. + lastGain)
1182 delta = (predictedConv - newConv)/((lastConv - estFinalConv)/(1 + lastGain))
1183 newGain = 1 - abs(delta)
1185 newGain = (newGain + lastGain)/2.
1186 gain = max(baseGain, newGain)
1189 gainList.append(gain)
1193 """Build an array that smoothly tapers to 0 away from detected sources.
1197 dcrModels : `lsst.pipe.tasks.DcrModel`
1198 Best fit model of the true sky after correcting chromatic effects.
1199 dcrBBox : `lsst.geom.box.Box2I`
1200 Sub-region of the coadd which includes a buffer to allow for DCR.
1204 weights : `numpy.ndarray` or `float`
1205 A 2D array of weight values that tapers smoothly to zero away from detected sources.
1206 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False.
1211 If ``useModelWeights`` is set and ``modelWeightsWidth`` is negative.
1213 if not self.config.useModelWeights:
1215 if self.config.modelWeightsWidth < 0:
1216 raise ValueError(
"modelWeightsWidth must not be negative if useModelWeights is set")
1217 convergeMask = dcrModels.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
1218 convergeMaskPixels = dcrModels.mask[dcrBBox].array & convergeMask > 0
1219 weights = np.zeros_like(dcrModels[0][dcrBBox].array)
1220 weights[convergeMaskPixels] = 1.
1221 weights = ndimage.filters.gaussian_filter(weights, self.config.modelWeightsWidth)
1222 weights /= np.max(weights)
1226 """Smoothly replace model pixel values with those from a
1227 reference at locations away from detected sources.
1231 modelImages : `list` of `lsst.afw.image.Image`
1232 The new DCR model images from the current iteration.
1233 The values will be modified in place.
1234 refImage : `lsst.afw.image.MaskedImage`
1235 A reference image used to supply the default pixel values.
1236 modelWeights : `numpy.ndarray` or `float`
1237 A 2D array of weight values that tapers smoothly to zero away from detected sources.
1238 Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False.
1240 if self.config.useModelWeights:
1241 for model
in modelImages:
1242 model.array *= modelWeights
1243 model.array += refImage.array*(1. - modelWeights)/self.config.dcrNumSubfilters
1246 """Pre-load sub-regions of a list of exposures.
1250 bbox : `lsst.geom.box.Box2I`
1252 statsCtrl : `lsst.afw.math.StatisticsControl`
1253 Statistics control object for coadd
1254 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
1255 `lsst.daf.persistence.ButlerDataRef`
1256 The data references to the input warped exposures.
1257 imageScalerList : `list` of `lsst.pipe.task.ImageScaler`
1258 The image scalars correct for the zero point of the exposures.
1259 spanSetMaskList : `list` of `dict` containing spanSet lists, or None
1260 Each element is dict with keys = mask plane name to add the spans to
1264 subExposures : `dict`
1265 The `dict` keys are the visit IDs,
1266 and the values are `lsst.afw.image.ExposureF`
1267 The pre-loaded exposures for the current subregion.
1268 The variance plane contains weights, and not the variance
1270 tempExpName = self.getTempExpDatasetName(self.warpType)
1271 zipIterables = zip(warpRefList, imageScalerList, spanSetMaskList)
1273 for warpExpRef, imageScaler, altMaskSpans
in zipIterables:
1274 if isinstance(warpExpRef, DeferredDatasetHandle):
1275 exposure = warpExpRef.get(parameters={
'bbox': bbox})
1277 exposure = warpExpRef.get(tempExpName +
"_sub", bbox=bbox)
1278 visit = warpExpRef.dataId[
"visit"]
1279 if altMaskSpans
is not None:
1280 self.applyAltMaskPlanes(exposure.mask, altMaskSpans)
1281 imageScaler.scaleMaskedImage(exposure.maskedImage)
1283 exposure.variance.array[:, :] = 0.
1285 exposure.variance.array[(exposure.mask.array & statsCtrl.getAndMask()) == 0] = 1.
1288 exposure.image.array[(exposure.mask.array & statsCtrl.getAndMask()) > 0] = 0.
1289 subExposures[visit] = exposure
1293 """Compute the PSF of the coadd from the exposures with the best seeing.
1297 templateCoadd : `lsst.afw.image.ExposureF`
1298 The initial coadd exposure before accounting for DCR.
1299 warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or
1300 `lsst.daf.persistence.ButlerDataRef`
1301 The data references to the input warped exposures.
1305 psf : `lsst.meas.algorithms.CoaddPsf`
1306 The average PSF of the input exposures with the best seeing.
1308 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
1309 tempExpName = self.getTempExpDatasetName(self.warpType)
1312 ccds = templateCoadd.getInfo().getCoaddInputs().ccds
1313 psfRefSize = templateCoadd.getPsf().computeShape().getDeterminantRadius()*sigma2fwhm
1314 psfSizes = np.zeros(len(ccds))
1315 ccdVisits = np.array(ccds[
"visit"])
1316 for warpExpRef
in warpRefList:
1317 if isinstance(warpExpRef, DeferredDatasetHandle):
1319 psf = warpExpRef.get(component=
"psf")
1322 psf = warpExpRef.get(tempExpName).getPsf()
1323 visit = warpExpRef.dataId[
"visit"]
1324 psfSize = psf.computeShape().getDeterminantRadius()*sigma2fwhm
1325 psfSizes[ccdVisits == visit] = psfSize
1329 sizeThreshold = min(np.median(psfSizes), psfRefSize)
1330 goodPsfs = psfSizes <= sizeThreshold
1331 psf = measAlg.CoaddPsf(ccds[goodPsfs], templateCoadd.getWcs(),
1332 self.config.coaddPsf.makeControl())