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.",
 
  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.psf
 
  562         dcrModels = DcrModel.fromImage(templateCoadd.maskedImage,
 
  563                                        self.config.dcrNumSubfilters,
 
  564                                        filterInfo=filterInfo,
 
  568     def run(self, skyInfo, warpRefList, imageScalerList, weightList,
 
  569             supplementaryData=None):
 
  570         """Assemble the coadd. 
  572         Requires additional inputs Struct ``supplementaryData`` to contain a 
  573         ``templateCoadd`` that serves as the model of the static sky. 
  575         Find artifacts and apply them to the warps' masks creating a list of 
  576         alternative masks with a new "CLIPPED" plane and updated "NO_DATA" plane 
  577         Then pass these alternative masks to the base class's assemble method. 
  579         Divide the ``templateCoadd`` evenly between each subfilter of a 
  580         ``DcrModel`` as the starting best estimate of the true wavelength- 
  581         dependent sky. Forward model the ``DcrModel`` using the known 
  582         chromatic effects in each subfilter and calculate a convergence metric 
  583         based on how well the modeled template matches the input warps. If 
  584         the convergence has not yet reached the desired threshold, then shift 
  585         and stack the residual images to build a new ``DcrModel``. Apply 
  586         conditioning to prevent oscillating solutions between iterations or 
  589         Once the ``DcrModel`` reaches convergence or the maximum number of 
  590         iterations has been reached, fill the metadata for each subfilter 
  591         image and make them proper ``coaddExposure``s. 
  595         skyInfo : `lsst.pipe.base.Struct` 
  596             Patch geometry information, from getSkyInfo 
  597         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
  598             `lsst.daf.persistence.ButlerDataRef` 
  599             The data references to the input warped exposures. 
  600         imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 
  601             The image scalars correct for the zero point of the exposures. 
  602         weightList : `list` of `float` 
  603             The weight to give each input exposure in the coadd 
  604         supplementaryData : `lsst.pipe.base.Struct` 
  605             Result struct returned by ``makeSupplementaryData`` with components: 
  607             - ``templateCoadd``: coadded exposure (`lsst.afw.image.Exposure`) 
  611         result : `lsst.pipe.base.Struct` 
  612            Result struct with components: 
  614             - ``coaddExposure``: coadded exposure (`lsst.afw.image.Exposure`) 
  615             - ``nImage``: exposure count image (`lsst.afw.image.ImageU`) 
  616             - ``dcrCoadds``: `list` of coadded exposures for each subfilter 
  617             - ``dcrNImages``: `list` of exposure count images for each subfilter 
  619         minNumIter = self.config.minNumIter 
or self.config.dcrNumSubfilters
 
  620         maxNumIter = self.config.maxNumIter 
or self.config.dcrNumSubfilters*3
 
  621         templateCoadd = supplementaryData.templateCoadd
 
  622         baseMask = templateCoadd.mask.clone()
 
  625         baseVariance = templateCoadd.variance.clone()
 
  626         baseVariance /= self.config.dcrNumSubfilters
 
  627         spanSetMaskList = self.findArtifacts(templateCoadd, warpRefList, imageScalerList)
 
  629         templateCoadd.setMask(baseMask)
 
  630         badMaskPlanes = self.config.badMaskPlanes[:]
 
  635         badPixelMask = templateCoadd.mask.getPlaneBitMask(badMaskPlanes)
 
  637         stats = self.prepareStats(mask=badPixelMask)
 
  638         dcrModels = self.prepareDcrInputs(templateCoadd, warpRefList, weightList)
 
  639         if self.config.doNImage:
 
  640             dcrNImages, dcrWeights = self.calculateNImage(dcrModels, skyInfo.bbox, warpRefList,
 
  641                                                           spanSetMaskList, stats.ctrl)
 
  642             nImage = afwImage.ImageU(skyInfo.bbox)
 
  646             for dcrNImage 
in dcrNImages:
 
  652         nSubregions = (
ceil(skyInfo.bbox.getHeight()/subregionSize[1])
 
  653                        * 
ceil(skyInfo.bbox.getWidth()/subregionSize[0]))
 
  655         for subBBox 
in self._subBBoxIter(skyInfo.bbox, subregionSize):
 
  658             self.log.
info(
"Computing coadd over patch %s subregion %s of %s: %s",
 
  659                           skyInfo.patchInfo.getIndex(), subIter, nSubregions, subBBox)
 
  661             dcrBBox.grow(self.bufferSize)
 
  662             dcrBBox.clip(dcrModels.bbox)
 
  663             modelWeights = self.calculateModelWeights(dcrModels, dcrBBox)
 
  664             subExposures = self.loadSubExposures(dcrBBox, stats.ctrl, warpRefList,
 
  665                                                  imageScalerList, spanSetMaskList)
 
  666             convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
 
  667                                                           warpRefList, weightList, stats.ctrl)
 
  668             self.log.
info(
"Initial convergence : %s", convergenceMetric)
 
  669             convergenceList = [convergenceMetric]
 
  671             convergenceCheck = 1.
 
  672             refImage = templateCoadd.image
 
  673             while (convergenceCheck > self.config.convergenceThreshold 
or modelIter <= minNumIter):
 
  674                 gain = self.calculateGain(convergenceList, gainList)
 
  675                 self.dcrAssembleSubregion(dcrModels, subExposures, subBBox, dcrBBox, warpRefList,
 
  676                                           stats.ctrl, convergenceMetric, gain,
 
  677                                           modelWeights, refImage, dcrWeights)
 
  678                 if self.config.useConvergence:
 
  679                     convergenceMetric = self.calculateConvergence(dcrModels, subExposures, subBBox,
 
  680                                                                   warpRefList, weightList, stats.ctrl)
 
  681                     if convergenceMetric == 0:
 
  682                         self.log.
warn(
"Coadd patch %s subregion %s had convergence metric of 0.0 which is " 
  683                                       "most likely due to there being no valid data in the region.",
 
  684                                       skyInfo.patchInfo.getIndex(), subIter)
 
  686                     convergenceCheck = (convergenceList[-1] - convergenceMetric)/convergenceMetric
 
  687                     if (convergenceCheck < 0) & (modelIter > minNumIter):
 
  688                         self.log.
warn(
"Coadd patch %s subregion %s diverged before reaching maximum " 
  689                                       "iterations or desired convergence improvement of %s." 
  691                                       skyInfo.patchInfo.getIndex(), subIter,
 
  692                                       self.config.convergenceThreshold, convergenceCheck)
 
  694                     convergenceList.append(convergenceMetric)
 
  695                 if modelIter > maxNumIter:
 
  696                     if self.config.useConvergence:
 
  697                         self.log.
warn(
"Coadd patch %s subregion %s reached maximum iterations " 
  698                                       "before reaching desired convergence improvement of %s." 
  699                                       " Final convergence improvement: %s",
 
  700                                       skyInfo.patchInfo.getIndex(), subIter,
 
  701                                       self.config.convergenceThreshold, convergenceCheck)
 
  704                 if self.config.useConvergence:
 
  705                     self.log.
info(
"Iteration %s with convergence metric %s, %.4f%% improvement (gain: %.2f)",
 
  706                                   modelIter, convergenceMetric, 100.*convergenceCheck, gain)
 
  709                 if self.config.useConvergence:
 
  710                     self.log.
info(
"Coadd patch %s subregion %s finished with " 
  711                                   "convergence metric %s after %s iterations",
 
  712                                   skyInfo.patchInfo.getIndex(), subIter, convergenceMetric, modelIter)
 
  714                     self.log.
info(
"Coadd patch %s subregion %s finished after %s iterations",
 
  715                                   skyInfo.patchInfo.getIndex(), subIter, modelIter)
 
  716             if self.config.useConvergence 
and convergenceMetric > 0:
 
  717                 self.log.
info(
"Final convergence improvement was %.4f%% overall",
 
  718                               100*(convergenceList[0] - convergenceMetric)/convergenceMetric)
 
  720         dcrCoadds = self.fillCoadd(dcrModels, skyInfo, warpRefList, weightList,
 
  721                                    calibration=self.scaleZeroPoint.getPhotoCalib(),
 
  722                                    coaddInputs=templateCoadd.getInfo().getCoaddInputs(),
 
  724                                    variance=baseVariance)
 
  725         coaddExposure = self.stackCoadd(dcrCoadds)
 
  726         return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
 
  727                                dcrCoadds=dcrCoadds, dcrNImages=dcrNImages)
 
  729     def calculateNImage(self, dcrModels, bbox, warpRefList, spanSetMaskList, statsCtrl):
 
  730         """Calculate the number of exposures contributing to each subfilter. 
  734         dcrModels : `lsst.pipe.tasks.DcrModel` 
  735             Best fit model of the true sky after correcting chromatic effects. 
  736         bbox : `lsst.geom.box.Box2I` 
  737             Bounding box of the patch to coadd. 
  738         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
  739             `lsst.daf.persistence.ButlerDataRef` 
  740             The data references to the input warped exposures. 
  741         spanSetMaskList : `list` of `dict` containing spanSet lists, or None 
  742             Each element of the `dict` contains the new mask plane name 
  743             (e.g. "CLIPPED and/or "NO_DATA") as the key, 
  744             and the list of SpanSets to apply to the mask. 
  745         statsCtrl : `lsst.afw.math.StatisticsControl` 
  746             Statistics control object for coadd 
  750         dcrNImages : `list` of `lsst.afw.image.ImageU` 
  751             List of exposure count images for each subfilter 
  752         dcrWeights : `list` of `lsst.afw.image.ImageF` 
  753             Per-pixel weights for each subfilter. 
  754             Equal to 1/(number of unmasked images contributing to each pixel). 
  756         dcrNImages = [afwImage.ImageU(bbox) 
for subfilter 
in range(self.config.dcrNumSubfilters)]
 
  757         dcrWeights = [afwImage.ImageF(bbox) 
for subfilter 
in range(self.config.dcrNumSubfilters)]
 
  758         tempExpName = self.getTempExpDatasetName(self.warpType)
 
  759         for warpExpRef, altMaskSpans 
in zip(warpRefList, spanSetMaskList):
 
  760             if isinstance(warpExpRef, DeferredDatasetHandle):
 
  762                 exposure = warpExpRef.get(parameters={
'bbox': bbox})
 
  765                 exposure = warpExpRef.get(tempExpName + 
"_sub", bbox=bbox)
 
  766             visitInfo = exposure.getInfo().getVisitInfo()
 
  767             wcs = exposure.getInfo().getWcs()
 
  769             if altMaskSpans 
is not None:
 
  770                 self.applyAltMaskPlanes(mask, altMaskSpans)
 
  771             weightImage = np.zeros_like(exposure.image.array)
 
  772             weightImage[(mask.array & statsCtrl.getAndMask()) == 0] = 1.
 
  775             weightsGenerator = self.dcrResiduals(weightImage, visitInfo, wcs, dcrModels.filter)
 
  776             for shiftedWeights, dcrNImage, dcrWeight 
in zip(weightsGenerator, dcrNImages, dcrWeights):
 
  777                 dcrNImage.array += np.rint(shiftedWeights).astype(dcrNImage.array.dtype)
 
  778                 dcrWeight.array += shiftedWeights
 
  780         weightsThreshold = 1.
 
  781         goodPix = dcrWeights[0].array > weightsThreshold
 
  782         for weights 
in dcrWeights[1:]:
 
  783             goodPix = (weights.array > weightsThreshold) & goodPix
 
  784         for subfilter 
in range(self.config.dcrNumSubfilters):
 
  785             dcrWeights[subfilter].array[goodPix] = 1./dcrWeights[subfilter].array[goodPix]
 
  786             dcrWeights[subfilter].array[~goodPix] = 0.
 
  787             dcrNImages[subfilter].array[~goodPix] = 0
 
  788         return (dcrNImages, dcrWeights)
 
  791                              statsCtrl, convergenceMetric,
 
  792                              gain, modelWeights, refImage, dcrWeights):
 
  793         """Assemble the DCR coadd for a sub-region. 
  795         Build a DCR-matched template for each input exposure, then shift the 
  796         residuals according to the DCR in each subfilter. 
  797         Stack the shifted residuals and apply them as a correction to the 
  798         solution from the previous iteration. 
  799         Restrict the new model solutions from varying by more than a factor of 
  800         `modelClampFactor` from the last solution, and additionally restrict the 
  801         individual subfilter models from varying by more than a factor of 
  802         `frequencyClampFactor` from their average. 
  803         Finally, mitigate potentially oscillating solutions by averaging the new 
  804         solution with the solution from the previous iteration, weighted by 
  805         their convergence metric. 
  809         dcrModels : `lsst.pipe.tasks.DcrModel` 
  810             Best fit model of the true sky after correcting chromatic effects. 
  811         subExposures : `dict` of `lsst.afw.image.ExposureF` 
  812             The pre-loaded exposures for the current subregion. 
  813         bbox : `lsst.geom.box.Box2I` 
  814             Bounding box of the subregion to coadd. 
  815         dcrBBox : `lsst.geom.box.Box2I` 
  816             Sub-region of the coadd which includes a buffer to allow for DCR. 
  817         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
  818             `lsst.daf.persistence.ButlerDataRef` 
  819             The data references to the input warped exposures. 
  820         statsCtrl : `lsst.afw.math.StatisticsControl` 
  821             Statistics control object for coadd 
  822         convergenceMetric : `float` 
  823             Quality of fit metric for the matched templates of the input images. 
  824         gain : `float`, optional 
  825             Relative weight to give the new solution when updating the model. 
  826         modelWeights : `numpy.ndarray` or `float` 
  827             A 2D array of weight values that tapers smoothly to zero away from detected sources. 
  828             Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 
  829         refImage : `lsst.afw.image.Image` 
  830             A reference image used to supply the default pixel values. 
  831         dcrWeights : `list` of `lsst.afw.image.Image` 
  832             Per-pixel weights for each subfilter. 
  833             Equal to 1/(number of unmasked images contributing to each pixel). 
  835         residualGeneratorList = []
 
  837         for warpExpRef 
in warpRefList:
 
  838             visit = warpExpRef.dataId[
"visit"]
 
  839             exposure = subExposures[visit]
 
  840             visitInfo = exposure.getInfo().getVisitInfo()
 
  841             wcs = exposure.getInfo().getWcs()
 
  842             templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
 
  843                                                            order=self.config.imageInterpOrder,
 
  844                                                            splitSubfilters=self.config.splitSubfilters,
 
  845                                                            splitThreshold=self.config.splitThreshold,
 
  846                                                            amplifyModel=self.config.accelerateModel)
 
  847             residual = exposure.image.array - templateImage.array
 
  849             residual *= exposure.variance.array
 
  853             residualGeneratorList.append(self.dcrResiduals(residual, visitInfo, wcs, dcrModels.filter))
 
  855         dcrSubModelOut = self.newModelFromResidual(dcrModels, residualGeneratorList, dcrBBox, statsCtrl,
 
  857                                                    modelWeights=modelWeights,
 
  859                                                    dcrWeights=dcrWeights)
 
  860         dcrModels.assign(dcrSubModelOut, bbox)
 
  863         """Prepare a residual image for stacking in each subfilter by applying the reverse DCR shifts. 
  867         residual : `numpy.ndarray` 
  868             The residual masked image for one exposure, 
  869             after subtracting the matched template 
  870         visitInfo : `lsst.afw.image.VisitInfo` 
  871             Metadata for the exposure. 
  872         wcs : `lsst.afw.geom.SkyWcs` 
  873             Coordinate system definition (wcs) for the exposure. 
  874         filterInfo : `lsst.afw.image.Filter` 
  875             The filter definition, set in the current instruments' obs package. 
  876             Required for any calculation of DCR, including making matched templates. 
  880         residualImage : `numpy.ndarray` 
  881             The residual image for the next subfilter, shifted for DCR. 
  885         filteredResidual = ndimage.spline_filter(residual, order=self.config.imageInterpOrder)
 
  888         dcrShift = 
calculateDcr(visitInfo, wcs, filterInfo, self.config.dcrNumSubfilters,
 
  889                                 splitSubfilters=
False)
 
  891             yield applyDcr(filteredResidual, dcr, useInverse=
True, splitSubfilters=
False,
 
  892                            doPrefilter=
False, order=self.config.imageInterpOrder)
 
  895                              gain, modelWeights, refImage, dcrWeights):
 
  896         """Calculate a new DcrModel from a set of image residuals. 
  900         dcrModels : `lsst.pipe.tasks.DcrModel` 
  901             Current model of the true sky after correcting chromatic effects. 
  902         residualGeneratorList : `generator` of `numpy.ndarray` 
  903             The residual image for the next subfilter, shifted for DCR. 
  904         dcrBBox : `lsst.geom.box.Box2I` 
  905             Sub-region of the coadd which includes a buffer to allow for DCR. 
  906         statsCtrl : `lsst.afw.math.StatisticsControl` 
  907             Statistics control object for coadd 
  909             Relative weight to give the new solution when updating the model. 
  910         modelWeights : `numpy.ndarray` or `float` 
  911             A 2D array of weight values that tapers smoothly to zero away from detected sources. 
  912             Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 
  913         refImage : `lsst.afw.image.Image` 
  914             A reference image used to supply the default pixel values. 
  915         dcrWeights : `list` of `lsst.afw.image.Image` 
  916             Per-pixel weights for each subfilter. 
  917             Equal to 1/(number of unmasked images contributing to each pixel). 
  921         dcrModel : `lsst.pipe.tasks.DcrModel` 
  922             New model of the true sky after correcting chromatic effects. 
  925         for subfilter, model 
in enumerate(dcrModels):
 
  926             residualsList = [
next(residualGenerator) 
for residualGenerator 
in residualGeneratorList]
 
  927             residual = np.sum(residualsList, axis=0)
 
  928             residual *= dcrWeights[subfilter][dcrBBox].array
 
  930             newModel = model[dcrBBox].
clone()
 
  931             newModel.array += residual
 
  933             badPixels = ~np.isfinite(newModel.array)
 
  934             newModel.array[badPixels] = model[dcrBBox].array[badPixels]
 
  935             if self.config.regularizeModelIterations > 0:
 
  936                 dcrModels.regularizeModelIter(subfilter, newModel, dcrBBox,
 
  937                                               self.config.regularizeModelIterations,
 
  938                                               self.config.regularizationWidth)
 
  939             newModelImages.append(newModel)
 
  940         if self.config.regularizeModelFrequency > 0:
 
  941             dcrModels.regularizeModelFreq(newModelImages, dcrBBox, statsCtrl,
 
  942                                           self.config.regularizeModelFrequency,
 
  943                                           self.config.regularizationWidth)
 
  944         dcrModels.conditionDcrModel(newModelImages, dcrBBox, gain=gain)
 
  945         self.applyModelWeights(newModelImages, refImage[dcrBBox], modelWeights)
 
  946         return DcrModel(newModelImages, dcrModels.filter, dcrModels.psf,
 
  947                         dcrModels.mask, dcrModels.variance)
 
  950         """Calculate a quality of fit metric for the matched templates. 
  954         dcrModels : `lsst.pipe.tasks.DcrModel` 
  955             Best fit model of the true sky after correcting chromatic effects. 
  956         subExposures : `dict` of `lsst.afw.image.ExposureF` 
  957             The pre-loaded exposures for the current subregion. 
  958         bbox : `lsst.geom.box.Box2I` 
  960         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
  961             `lsst.daf.persistence.ButlerDataRef` 
  962             The data references to the input warped exposures. 
  963         weightList : `list` of `float` 
  964             The weight to give each input exposure in the coadd 
  965         statsCtrl : `lsst.afw.math.StatisticsControl` 
  966             Statistics control object for coadd 
  970         convergenceMetric : `float` 
  971             Quality of fit metric for all input exposures, within the sub-region 
  973         significanceImage = np.abs(dcrModels.getReferenceImage(bbox))
 
  975         significanceImage += nSigma*dcrModels.calculateNoiseCutoff(dcrModels[1], statsCtrl,
 
  976                                                                    bufferSize=self.bufferSize)
 
  977         if np.max(significanceImage) == 0:
 
  978             significanceImage += 1.
 
  982         for warpExpRef, expWeight 
in zip(warpRefList, weightList):
 
  983             visit = warpExpRef.dataId[
"visit"]
 
  984             exposure = subExposures[visit][bbox]
 
  985             singleMetric = self.calculateSingleConvergence(dcrModels, exposure, significanceImage, statsCtrl)
 
  986             metric += singleMetric
 
  987             metricList[visit] = singleMetric
 
  989         self.log.
info(
"Individual metrics:\n%s", metricList)
 
  990         return 1.0 
if weight == 0.0 
else metric/weight
 
  993         """Calculate a quality of fit metric for a single matched template. 
  997         dcrModels : `lsst.pipe.tasks.DcrModel` 
  998             Best fit model of the true sky after correcting chromatic effects. 
  999         exposure : `lsst.afw.image.ExposureF` 
 1000             The input warped exposure to evaluate. 
 1001         significanceImage : `numpy.ndarray` 
 1002             Array of weights for each pixel corresponding to its significance 
 1003             for the convergence calculation. 
 1004         statsCtrl : `lsst.afw.math.StatisticsControl` 
 1005             Statistics control object for coadd 
 1009         convergenceMetric : `float` 
 1010             Quality of fit metric for one exposure, within the sub-region. 
 1012         convergeMask = exposure.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
 
 1013         templateImage = dcrModels.buildMatchedTemplate(exposure=exposure,
 
 1014                                                        order=self.config.imageInterpOrder,
 
 1015                                                        splitSubfilters=self.config.splitSubfilters,
 
 1016                                                        splitThreshold=self.config.splitThreshold,
 
 1017                                                        amplifyModel=self.config.accelerateModel)
 
 1018         diffVals = np.abs(exposure.image.array - templateImage.array)*significanceImage
 
 1019         refVals = np.abs(exposure.image.array + templateImage.array)*significanceImage/2.
 
 1021         finitePixels = np.isfinite(diffVals)
 
 1022         goodMaskPixels = (exposure.mask.array & statsCtrl.getAndMask()) == 0
 
 1023         convergeMaskPixels = exposure.mask.array & convergeMask > 0
 
 1024         usePixels = finitePixels & goodMaskPixels & convergeMaskPixels
 
 1025         if np.sum(usePixels) == 0:
 
 1028             diffUse = diffVals[usePixels]
 
 1029             refUse = refVals[usePixels]
 
 1030             metric = np.sum(diffUse/np.median(diffUse))/np.sum(refUse/np.median(diffUse))
 
 1034         """Add a list of sub-band coadds together. 
 1038         dcrCoadds : `list` of `lsst.afw.image.ExposureF` 
 1039             A list of coadd exposures, each exposure containing 
 1040             the model for one subfilter. 
 1044         coaddExposure : `lsst.afw.image.ExposureF` 
 1045             A single coadd exposure that is the sum of the sub-bands. 
 1047         coaddExposure = dcrCoadds[0].
clone()
 
 1048         for coadd 
in dcrCoadds[1:]:
 
 1049             coaddExposure.maskedImage += coadd.maskedImage
 
 1050         return coaddExposure
 
 1052     def fillCoadd(self, dcrModels, skyInfo, warpRefList, weightList, calibration=None, coaddInputs=None,
 
 1053                   mask=None, variance=None):
 
 1054         """Create a list of coadd exposures from a list of masked images. 
 1058         dcrModels : `lsst.pipe.tasks.DcrModel` 
 1059             Best fit model of the true sky after correcting chromatic effects. 
 1060         skyInfo : `lsst.pipe.base.Struct` 
 1061             Patch geometry information, from getSkyInfo 
 1062         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
 1063             `lsst.daf.persistence.ButlerDataRef` 
 1064             The data references to the input warped exposures. 
 1065         weightList : `list` of `float` 
 1066             The weight to give each input exposure in the coadd 
 1067         calibration : `lsst.afw.Image.PhotoCalib`, optional 
 1068             Scale factor to set the photometric calibration of an exposure. 
 1069         coaddInputs : `lsst.afw.Image.CoaddInputs`, optional 
 1070             A record of the observations that are included in the coadd. 
 1071         mask : `lsst.afw.image.Mask`, optional 
 1072             Optional mask to override the values in the final coadd. 
 1073         variance : `lsst.afw.image.Image`, optional 
 1074             Optional variance plane to override the values in the final coadd. 
 1078         dcrCoadds : `list` of `lsst.afw.image.ExposureF` 
 1079             A list of coadd exposures, each exposure containing 
 1080             the model for one subfilter. 
 1083         refModel = dcrModels.getReferenceImage()
 
 1084         for model 
in dcrModels:
 
 1085             if self.config.accelerateModel > 1:
 
 1086                 model.array = (model.array - refModel)*self.config.accelerateModel + refModel
 
 1087             coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
 
 1088             if calibration 
is not None:
 
 1089                 coaddExposure.setPhotoCalib(calibration)
 
 1090             if coaddInputs 
is not None:
 
 1091                 coaddExposure.getInfo().setCoaddInputs(coaddInputs)
 
 1093             self.assembleMetadata(coaddExposure, warpRefList, weightList)
 
 1095             coaddExposure.setPsf(dcrModels.psf)
 
 1097             maskedImage = afwImage.MaskedImageF(dcrModels.bbox)
 
 1098             maskedImage.image = model
 
 1099             maskedImage.mask = dcrModels.mask
 
 1100             maskedImage.variance = dcrModels.variance
 
 1101             coaddExposure.setMaskedImage(maskedImage[skyInfo.bbox])
 
 1102             coaddExposure.setPhotoCalib(self.scaleZeroPoint.getPhotoCalib())
 
 1103             if mask 
is not None:
 
 1104                 coaddExposure.setMask(mask)
 
 1105             if variance 
is not None:
 
 1106                 coaddExposure.setVariance(variance)
 
 1107             dcrCoadds.append(coaddExposure)
 
 1111         """Calculate the gain to use for the current iteration. 
 1113         After calculating a new DcrModel, each value is averaged with the 
 1114         value in the corresponding pixel from the previous iteration. This 
 1115         reduces oscillating solutions that iterative techniques are plagued by, 
 1116         and speeds convergence. By far the biggest changes to the model 
 1117         happen in the first couple iterations, so we can also use a more 
 1118         aggressive gain later when the model is changing slowly. 
 1122         convergenceList : `list` of `float` 
 1123             The quality of fit metric from each previous iteration. 
 1124         gainList : `list` of `float` 
 1125             The gains used in each previous iteration: appended with the new 
 1127             Gains are numbers between ``self.config.baseGain`` and 1. 
 1132             Relative weight to give the new solution when updating the model. 
 1133             A value of 1.0 gives equal weight to both solutions. 
 1138             If ``len(convergenceList) != len(gainList)+1``. 
 1140         nIter = len(convergenceList)
 
 1141         if nIter != len(gainList) + 1:
 
 1142             raise ValueError(
"convergenceList (%d) must be one element longer than gainList (%d)." 
 1143                              % (len(convergenceList), len(gainList)))
 
 1145         if self.config.baseGain 
is None:
 
 1148             baseGain = 1./(self.config.dcrNumSubfilters - 1)
 
 1150             baseGain = self.config.baseGain
 
 1152         if self.config.useProgressiveGain 
and nIter > 2:
 
 1160             estFinalConv = [((1 + gainList[i])*convergenceList[i + 1] - convergenceList[i])/gainList[i]
 
 1161                             for i 
in range(nIter - 1)]
 
 1164             estFinalConv = np.array(estFinalConv)
 
 1165             estFinalConv[estFinalConv < 0] = 0
 
 1167             estFinalConv = np.median(estFinalConv[
max(nIter - 5, 0):])
 
 1168             lastGain = gainList[-1]
 
 1169             lastConv = convergenceList[-2]
 
 1170             newConv = convergenceList[-1]
 
 1175             predictedConv = (estFinalConv*lastGain + lastConv)/(1. + lastGain)
 
 1181             delta = (predictedConv - newConv)/((lastConv - estFinalConv)/(1 + lastGain))
 
 1182             newGain = 1 - 
abs(delta)
 
 1184             newGain = (newGain + lastGain)/2.
 
 1185             gain = 
max(baseGain, newGain)
 
 1188         gainList.append(gain)
 
 1192         """Build an array that smoothly tapers to 0 away from detected sources. 
 1196         dcrModels : `lsst.pipe.tasks.DcrModel` 
 1197             Best fit model of the true sky after correcting chromatic effects. 
 1198         dcrBBox : `lsst.geom.box.Box2I` 
 1199             Sub-region of the coadd which includes a buffer to allow for DCR. 
 1203         weights : `numpy.ndarray` or `float` 
 1204             A 2D array of weight values that tapers smoothly to zero away from detected sources. 
 1205             Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 
 1210             If ``useModelWeights`` is set and ``modelWeightsWidth`` is negative. 
 1212         if not self.config.useModelWeights:
 
 1214         if self.config.modelWeightsWidth < 0:
 
 1215             raise ValueError(
"modelWeightsWidth must not be negative if useModelWeights is set")
 
 1216         convergeMask = dcrModels.mask.getPlaneBitMask(self.config.convergenceMaskPlanes)
 
 1217         convergeMaskPixels = dcrModels.mask[dcrBBox].array & convergeMask > 0
 
 1218         weights = np.zeros_like(dcrModels[0][dcrBBox].array)
 
 1219         weights[convergeMaskPixels] = 1.
 
 1220         weights = ndimage.filters.gaussian_filter(weights, self.config.modelWeightsWidth)
 
 1221         weights /= np.max(weights)
 
 1225         """Smoothly replace model pixel values with those from a 
 1226         reference at locations away from detected sources. 
 1230         modelImages : `list` of `lsst.afw.image.Image` 
 1231             The new DCR model images from the current iteration. 
 1232             The values will be modified in place. 
 1233         refImage : `lsst.afw.image.MaskedImage` 
 1234             A reference image used to supply the default pixel values. 
 1235         modelWeights : `numpy.ndarray` or `float` 
 1236             A 2D array of weight values that tapers smoothly to zero away from detected sources. 
 1237             Set to a placeholder value of 1.0 if ``self.config.useModelWeights`` is False. 
 1239         if self.config.useModelWeights:
 
 1240             for model 
in modelImages:
 
 1241                 model.array *= modelWeights
 
 1242                 model.array += refImage.array*(1. - modelWeights)/self.config.dcrNumSubfilters
 
 1245         """Pre-load sub-regions of a list of exposures. 
 1249         bbox : `lsst.geom.box.Box2I` 
 1251         statsCtrl : `lsst.afw.math.StatisticsControl` 
 1252             Statistics control object for coadd 
 1253         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
 1254             `lsst.daf.persistence.ButlerDataRef` 
 1255             The data references to the input warped exposures. 
 1256         imageScalerList : `list` of `lsst.pipe.task.ImageScaler` 
 1257             The image scalars correct for the zero point of the exposures. 
 1258         spanSetMaskList : `list` of `dict` containing spanSet lists, or None 
 1259             Each element is dict with keys = mask plane name to add the spans to 
 1263         subExposures : `dict` 
 1264             The `dict` keys are the visit IDs, 
 1265             and the values are `lsst.afw.image.ExposureF` 
 1266             The pre-loaded exposures for the current subregion. 
 1267             The variance plane contains weights, and not the variance 
 1269         tempExpName = self.getTempExpDatasetName(self.warpType)
 
 1270         zipIterables = zip(warpRefList, imageScalerList, spanSetMaskList)
 
 1272         for warpExpRef, imageScaler, altMaskSpans 
in zipIterables:
 
 1273             if isinstance(warpExpRef, DeferredDatasetHandle):
 
 1274                 exposure = warpExpRef.get(parameters={
'bbox': bbox})
 
 1276                 exposure = warpExpRef.get(tempExpName + 
"_sub", bbox=bbox)
 
 1277             visit = warpExpRef.dataId[
"visit"]
 
 1278             if altMaskSpans 
is not None:
 
 1279                 self.applyAltMaskPlanes(exposure.mask, altMaskSpans)
 
 1280             imageScaler.scaleMaskedImage(exposure.maskedImage)
 
 1282             exposure.variance.array[:, :] = 0.
 
 1284             exposure.variance.array[(exposure.mask.array & statsCtrl.getAndMask()) == 0] = 1.
 
 1287             exposure.image.array[(exposure.mask.array & statsCtrl.getAndMask()) > 0] = 0.
 
 1288             subExposures[visit] = exposure
 
 1292         """Compute the PSF of the coadd from the exposures with the best seeing. 
 1296         templateCoadd : `lsst.afw.image.ExposureF` 
 1297             The initial coadd exposure before accounting for DCR. 
 1298         warpRefList : `list` of `lsst.daf.butler.DeferredDatasetHandle` or 
 1299             `lsst.daf.persistence.ButlerDataRef` 
 1300             The data references to the input warped exposures. 
 1304         psf : `lsst.meas.algorithms.CoaddPsf` 
 1305             The average PSF of the input exposures with the best seeing. 
 1307         sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
 
 1308         tempExpName = self.getTempExpDatasetName(self.warpType)
 
 1311         ccds = templateCoadd.getInfo().getCoaddInputs().ccds
 
 1312         psfRefSize = templateCoadd.getPsf().computeShape().getDeterminantRadius()*sigma2fwhm
 
 1313         psfSizes = np.zeros(len(ccds))
 
 1314         ccdVisits = np.array(ccds[
"visit"])
 
 1315         for warpExpRef 
in warpRefList:
 
 1316             if isinstance(warpExpRef, DeferredDatasetHandle):
 
 1318                 psf = warpExpRef.get(component=
"psf")
 
 1321                 psf = warpExpRef.get(tempExpName).getPsf()
 
 1322             visit = warpExpRef.dataId[
"visit"]
 
 1323             psfSize = psf.computeShape().getDeterminantRadius()*sigma2fwhm
 
 1324             psfSizes[ccdVisits == visit] = psfSize
 
 1328         sizeThreshold = 
min(np.median(psfSizes), psfRefSize)
 
 1329         goodPsfs = psfSizes <= sizeThreshold
 
 1330         psf = measAlg.CoaddPsf(ccds[goodPsfs], templateCoadd.getWcs(),
 
 1331                                self.config.coaddPsf.makeControl())