1 from __future__
import division, absolute_import
31 from .coaddBase
import CoaddBaseTask, SelectDataIdContainer
32 from .interpImage
import InterpImageTask
33 from .matchBackgrounds
import MatchBackgroundsTask
34 from .scaleZeroPoint
import ScaleZeroPointTask
35 from .coaddHelpers
import groupPatchExposures, getGroupDataRef
37 __all__ = [
"AssembleCoaddTask"]
40 subregionSize = pexConfig.ListField(
42 doc =
"Width, height of stack subregion size; " \
43 "make small enough that a full stack of images will fit into memory at once.",
45 default = (2000, 2000),
47 doSigmaClip = pexConfig.Field(
49 doc =
"Perform sigma clipped outlier rejection? If False then compute a simple mean.",
52 sigmaClip = pexConfig.Field(
54 doc =
"Sigma for outlier rejection; ignored if doSigmaClip false.",
57 clipIter = pexConfig.Field(
59 doc =
"Number of iterations of outlier rejection; ignored if doSigmaClip false.",
62 scaleZeroPoint = pexConfig.ConfigurableField(
63 target = ScaleZeroPointTask,
64 doc =
"Task to adjust the photometric zero point of the coadd temp exposures",
66 doInterp = pexConfig.Field(
67 doc =
"Interpolate over NaN pixels? Also extrapolate, if necessary, but the results are ugly.",
71 interpImage = pexConfig.ConfigurableField(
72 target = InterpImageTask,
73 doc =
"Task to interpolate (and extrapolate) over NaN pixels",
75 matchBackgrounds = pexConfig.ConfigurableField(
76 target = MatchBackgroundsTask,
77 doc =
"Task to match backgrounds",
79 maxMatchResidualRatio = pexConfig.Field(
80 doc =
"Maximum ratio of the mean squared error of the background matching model to the variance " \
81 "of the difference in backgrounds",
85 maxMatchResidualRMS = pexConfig.Field(
86 doc =
"Maximum RMS of residuals of the background offset fit in matchBackgrounds.",
90 doWrite = pexConfig.Field(
91 doc =
"Persist coadd?",
95 doMatchBackgrounds = pexConfig.Field(
96 doc =
"Match backgrounds of coadd temp exposures before coadding them? " \
97 "If False, the coadd temp expsosures must already have been background subtracted or matched",
101 autoReference = pexConfig.Field(
102 doc =
"Automatically select the coadd temp exposure to use as a reference for background matching? " \
103 "Ignored if doMatchBackgrounds false. " \
104 "If False you must specify the reference temp exposure as the data Id",
108 badMaskPlanes = pexConfig.ListField(
110 doc =
"Mask planes that, if set, the associated pixel should not be included in the coaddTempExp.",
116 """Assemble a coadd from a set of coaddTempExp
118 ConfigClass = AssembleCoaddConfig
119 _DefaultName =
"assembleCoadd"
122 CoaddBaseTask.__init__(self, *args, **kwargs)
123 self.makeSubtask(
"interpImage")
124 self.makeSubtask(
"matchBackgrounds")
125 self.makeSubtask(
"scaleZeroPoint")
128 def run(self, dataRef, selectDataList=[]):
129 """Assemble a coadd from a set of coaddTempExp
131 The coadd is computed as a mean with optional outlier rejection.
133 assembleCoaddTask only works on the dataset type 'coaddTempExp', which are 'coadd temp exposures'.
134 Each coaddTempExp is the size of a patch and contains data for one run, visit or
135 (for a non-mosaic camera it will contain data for a single exposure).
137 coaddTempExps, by default, will have backgrounds in them and will require
138 config.doMatchBackgrounds = True. However, makeCoaddTempExp.py can optionally create background-
139 subtracted coaddTempExps which can be coadded here by setting
140 config.doMatchBackgrounds = False.
142 @param dataRef: data reference for a coadd patch (of dataType 'Coadd') OR a data reference
143 for a coadd temp exposure (of dataType 'Coadd_tempExp') which serves as the reference visit
144 if config.doMatchBackgrounds true and config.autoReference false)
145 If supplying a coadd patch: Must include keys "tract", "patch",
146 plus the camera-specific filter key (e.g. "filter")
147 Used to access the following data products (depending on the config):
148 - [in] self.config.coaddName + "Coadd_tempExp"
149 - [out] self.config.coaddName + "Coadd"
151 @return: a pipeBase.Struct with fields:
152 - coaddExposure: coadd exposure
154 skyInfo = self.getSkyInfo(dataRef)
155 calExpRefList = self.selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
156 if len(calExpRefList) == 0:
157 self.log.warn(
"No exposures to coadd")
159 self.log.info(
"Coadding %d exposures" % len(calExpRefList))
161 butler = dataRef.getButler()
163 self.getTempExpDatasetName())
164 tempExpRefList = [
getGroupDataRef(butler, self.getTempExpDatasetName(), g, groupData.keys)
for
165 g
in groupData.groups.keys()]
167 tempExpRefList = inputData.tempExpRefList
168 self.log.info(
"Found %d %s" % (len(inputData.tempExpRefList), self.getTempExpDatasetName()))
169 if len(inputData.tempExpRefList) == 0:
170 self.log.warn(
"No coadd temporary exposures found")
172 if self.config.doMatchBackgrounds:
175 if len(inputData.tempExpRefList) == 0:
176 self.log.warn(
"No valid background models")
179 coaddExp = self.
assemble(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
180 inputData.weightList,
181 inputData.backgroundInfoList
if self.config.doMatchBackgrounds
else None)
182 if self.config.doMatchBackgrounds:
184 inputData.backgroundInfoList)
186 if self.config.doInterp:
187 self.interpImage.interpolateOnePlane(
188 maskedImage = coaddExp.getMaskedImage(),
192 if self.config.doWrite:
193 self.writeCoaddOutput(dataRef, coaddExp)
195 return pipeBase.Struct(coaddExposure=coaddExp)
199 """Construct an image scaler for the background reference frame
201 If there is no reference frame ('autoReference'), then this is a no-op
204 @param dataRef: Data reference for the background reference frame, or None
205 @return image scaler, or None
207 if self.config.autoReference:
211 dataset = self.getTempExpDatasetName()
212 if not dataRef.datasetExists(dataset):
213 raise RuntimeError(
"Could not find reference exposure %s %s." % (dataset, dataRef.dataId))
215 refExposure = dataRef.get(self.getTempExpDatasetName(), immediate=
True)
216 refImageScaler = self.scaleZeroPoint.computeImageScaler(
217 exposure = refExposure,
220 return refImageScaler
224 """Prepare the input warps for coaddition
226 This involves measuring weights and constructing image scalers
227 for each of the inputs.
229 @param refList: List of data references to tempExp
231 - tempExprefList: List of data references to tempExp
232 - weightList: List of weightings
233 - imageScalerList: List of image scalers
236 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
237 statsCtrl.setNumIter(self.config.clipIter)
238 statsCtrl.setAndMask(self.getBadPixelMask())
239 statsCtrl.setNanSafe(
True)
247 tempExpName = self.getTempExpDatasetName()
248 for tempExpRef
in refList:
249 if not tempExpRef.datasetExists(tempExpName):
250 self.log.warn(
"Could not find %s %s; skipping it" % (tempExpName, tempExpRef.dataId))
253 tempExp = tempExpRef.get(tempExpName, immediate=
True)
254 maskedImage = tempExp.getMaskedImage()
255 imageScaler = self.scaleZeroPoint.computeImageScaler(
257 dataRef = tempExpRef,
260 imageScaler.scaleMaskedImage(maskedImage)
262 self.log.warn(
"Scaling failed for %s (skipping it): %s" % (tempExpRef.dataId, e))
265 afwMath.MEANCLIP, statsCtrl)
266 meanVar, meanVarErr = statObj.getResult(afwMath.MEANCLIP);
267 weight = 1.0 / float(meanVar)
268 if not numpy.isfinite(weight):
269 self.log.warn(
"Non-finite weight for %s: skipping" % (tempExpRef.dataId,))
271 self.log.info(
"Weight of %s %s = %0.3f" % (tempExpName, tempExpRef.dataId, weight))
276 tempExpRefList.append(tempExpRef)
277 weightList.append(weight)
278 imageScalerList.append(imageScaler)
280 return pipeBase.Struct(tempExpRefList=tempExpRefList, weightList=weightList,
281 imageScalerList=imageScalerList)
285 """Perform background matching on the prepared inputs
287 If no reference is provided, the background matcher will select one.
289 This method returns a new inputData Struct that can replace the original.
291 @param inputData: Struct from prepareInputs() with tempExpRefList, weightList, imageScalerList
292 @param refExpDataRef: Data reference for background reference tempExp, or None
293 @param refImageScaler: Image scaler for background reference tempExp, or None
295 - tempExprefList: List of data references to tempExp
296 - weightList: List of weightings
297 - imageScalerList: List of image scalers
298 - backgroundInfoList: result from background matching
301 backgroundInfoList = self.matchBackgrounds.run(
302 expRefList = inputData.tempExpRefList,
303 imageScalerList = inputData.imageScalerList,
304 refExpDataRef = refExpDataRef
if not self.config.autoReference
else None,
305 refImageScaler = refImageScaler,
306 expDatasetType = self.getTempExpDatasetName(),
309 self.log.fatal(
"Cannot match backgrounds: %s" % (e))
310 raise pipeBase.TaskError(
"Background matching failed.")
313 newTempExpRefList = []
314 newBackgroundStructList = []
318 for tempExpRef, bgInfo, scaler, weight
in zip(inputData.tempExpRefList, backgroundInfoList,
319 inputData.imageScalerList, inputData.weightList):
320 if not bgInfo.isReference:
323 if (bgInfo.backgroundModel
is None):
324 self.log.info(
"No background offset model available for %s: skipping"%(
328 varianceRatio = bgInfo.matchedMSE / bgInfo.diffImVar
330 self.log.info(
"MSE/Var ratio not calculable (%s) for %s: skipping" %
331 (e, tempExpRef.dataId,))
333 if not numpy.isfinite(varianceRatio):
334 self.log.info(
"MSE/Var ratio not finite (%.2f / %.2f) for %s: skipping" %
335 (bgInfo.matchedMSE, bgInfo.diffImVar,
338 elif (varianceRatio > self.config.maxMatchResidualRatio):
339 self.log.info(
"Bad fit. MSE/Var ratio %.2f > %.2f for %s: skipping" % (
340 varianceRatio, self.config.maxMatchResidualRatio, tempExpRef.dataId,))
342 elif ( bgInfo.fitRMS > self.config.maxMatchResidualRMS):
343 self.log.info(
"Bad fit. RMS %.2f > %.2f for %s: skipping" % (
344 bgInfo.fitRMS, self.config.maxMatchResidualRMS, tempExpRef.dataId,))
346 newWeightList.append(1 / (1 / weight + bgInfo.fitRMS**2))
347 newTempExpRefList.append(tempExpRef)
348 newBackgroundStructList.append(bgInfo)
349 newScaleList.append(scaler)
351 return pipeBase.Struct(tempExpRefList=newTempExpRefList, weightList=newWeightList,
352 imageScalerList=newScaleList, backgroundInfoList=newBackgroundStructList)
354 def assemble(self, skyInfo, tempExpRefList, imageScalerList, weightList, bgInfoList=None):
355 """Assemble a coadd from input warps
357 The assembly is performed over small areas on the image at a time, to
358 conserve memory usage.
360 @param skyInfo: Patch geometry information, from getSkyInfo
361 @param tempExpRefList: List of data references to tempExp
362 @param imageScalerList: List of image scalers
363 @param weightList: List of weights
364 @param bgInfoList: List of background data from background matching
365 @return coadded exposure
367 tempExpName = self.getTempExpDatasetName()
368 self.log.info(
"Assembling %s %s" % (len(tempExpRefList), tempExpName))
371 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
372 statsCtrl.setNumIter(self.config.clipIter)
373 statsCtrl.setAndMask(self.getBadPixelMask())
374 statsCtrl.setNanSafe(
True)
375 statsCtrl.setCalcErrorFromInputVariance(
True)
377 if self.config.doSigmaClip:
378 statsFlags = afwMath.MEANCLIP
380 statsFlags = afwMath.MEAN
382 if bgInfoList
is None:
383 bgInfoList = [
None]*len(tempExpRefList)
385 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
386 coaddExposure.setCalib(self.scaleZeroPoint.getCalib())
387 coaddExposure.getInfo().setCoaddInputs(self.inputRecorder.makeCoaddInputs())
389 coaddMaskedImage = coaddExposure.getMaskedImage()
390 subregionSizeArr = self.config.subregionSize
392 for subBBox
in _subBBoxIter(skyInfo.bbox, subregionSize):
395 weightList, bgInfoList, statsFlags, statsCtrl)
397 self.log.fatal(
"Cannot compute coadd %s: %s" % (subBBox, e,))
404 """Set the metadata for the coadd
406 This basic implementation simply sets the filter from the
409 @param coaddExposure: The target image for the coadd
410 @param tempExpRefList: List of data references to tempExp
411 @param weightList: List of weights
413 tempExpName = self.getTempExpDatasetName()
418 coaddInputs = coaddExposure.getInfo().getCoaddInputs()
419 for tempExpRef, weight
in zip(tempExpRefList, weightList):
420 tempExp = tempExpRef.get(tempExpName +
"_sub", bbox=bbox, imageOrigin=
"LOCAL", immediate=
True)
422 coaddExposure.setFilter(tempExp.getFilter())
424 self.inputRecorder.addVisitToCoadd(coaddInputs, tempExp, weight)
425 coaddInputs.visits.sort()
426 if self.config.doPsfMatch:
427 psf = self.config.modelPsf.apply()
430 coaddExposure.setPsf(psf)
432 def assembleSubregion(self, coaddExposure, bbox, tempExpRefList, imageScalerList, weightList,
433 bgInfoList, statsFlags, statsCtrl):
434 """Assemble the coadd for a sub-region
436 @param coaddExposure: The target image for the coadd
437 @param bbox: Sub-region to coadd
438 @param tempExpRefList: List of data reference to tempExp
439 @param imageScalerList: List of image scalers
440 @param weightList: List of weights
441 @param bgInfoList: List of background data from background matching
442 @param statsFlags: Statistic for coadd
443 @param statsCtrl: Statistics control object for coadd
445 self.log.info(
"Computing coadd over %s" % bbox)
446 tempExpName = self.getTempExpDatasetName()
447 coaddMaskedImage = coaddExposure.getMaskedImage()
448 coaddView = afwImage.MaskedImageF(coaddMaskedImage, bbox, afwImage.PARENT,
False)
449 maskedImageList = afwImage.vectorMaskedImageF()
450 for tempExpRef, imageScaler, bgInfo
in zip(tempExpRefList, imageScalerList, bgInfoList):
451 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
452 maskedImage = exposure.getMaskedImage()
453 imageScaler.scaleMaskedImage(maskedImage)
455 if self.config.doMatchBackgrounds
and not bgInfo.isReference:
456 backgroundModel = bgInfo.backgroundModel
457 backgroundImage = backgroundModel.getImage()
if \
458 self.matchBackgrounds.config.usePolynomial
else \
459 backgroundModel.getImageF()
460 backgroundImage.setXY0(coaddMaskedImage.getXY0())
461 maskedImage += backgroundImage.Factory(backgroundImage, bbox, afwImage.PARENT,
False)
462 var = maskedImage.getVariance()
463 var += (bgInfo.fitRMS)**2
465 maskedImageList.append(maskedImage)
467 with self.timer(
"stack"):
469 maskedImageList, statsFlags, statsCtrl, weightList)
471 coaddView <<= coaddSubregion
475 """Add metadata from the background matching to the coadd
477 @param coaddExposure: Coadd
478 @param backgroundInfoList: List of background info, results from background matching
480 self.log.info(
"Adding exposure information to metadata")
481 metadata = coaddExposure.getMetadata()
482 metadata.addString(
"CTExp_SDQA1_DESCRIPTION",
483 "Background matching: Ratio of matchedMSE / diffImVar")
484 for ind, (tempExpRef, backgroundInfo)
in enumerate(zip(tempExpRefList, backgroundInfoList)):
485 tempExpStr =
'&'.join(
'%s=%s' % (k,v)
for k,v
in tempExpRef.dataId.items())
486 if backgroundInfo.isReference:
487 metadata.addString(
"ReferenceExp_ID", tempExpStr)
489 metadata.addString(
"CTExp_ID_%d" % (ind), tempExpStr)
490 metadata.addDouble(
"CTExp_SDQA1_%d" % (ind),
491 backgroundInfo.matchedMSE/backgroundInfo.diffImVar)
492 metadata.addDouble(
"CTExp_SDQA2_%d" % (ind),
493 backgroundInfo.fitRMS)
496 """Create an argument parser
498 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
499 parser.add_id_argument(
"--id", cls.ConfigClass().coaddName +
"Coadd_tempExp",
500 help=
"data ID, e.g. --id tract=12345 patch=1,2",
501 ContainerClass=AssembleCoaddDataIdContainer)
502 parser.add_id_argument(
"--selectId",
"calexp", help=
"data ID, e.g. --selectId visit=6789 ccd=0..9",
503 ContainerClass=SelectDataIdContainer)
509 """Iterate over subregions of a bbox
511 @param[in] bbox: bounding box over which to iterate: afwGeom.Box2I
512 @param[in] subregionSize: size of sub-bboxes
514 @return subBBox: next sub-bounding box of size subregionSize or smaller;
515 each subBBox is contained within bbox, so it may be smaller than subregionSize at the edges of bbox,
516 but it will never be empty
519 raise RuntimeError(
"bbox %s is empty" % (bbox,))
520 if subregionSize[0] < 1
or subregionSize[1] < 1:
521 raise RuntimeError(
"subregionSize %s must be nonzero" % (subregionSize,))
523 for rowShift
in range(0, bbox.getHeight(), subregionSize[1]):
524 for colShift
in range(0, bbox.getWidth(), subregionSize[0]):
527 if subBBox.isEmpty():
528 raise RuntimeError(
"Bug: empty bbox! bbox=%s, subregionSize=%s, colShift=%s, rowShift=%s" % \
529 (bbox, subregionSize, colShift, rowShift))
535 """A version of lsst.pipe.base.DataIdContainer specialized for assembleCoadd.
538 """Make self.refList from self.idList.
540 Interpret the config.doMatchBackgrounds, config.autoReference,
541 and whether a visit/run supplied.
542 If a visit/run is supplied, config.autoReference is automatically set to False.
543 if config.doMatchBackgrounds == false, then a visit/run will be ignored if accidentally supplied.
546 keysCoadd = namespace.butler.getKeys(datasetType=namespace.config.coaddName +
"Coadd",
548 keysCoaddTempExp = namespace.butler.getKeys(datasetType=namespace.config.coaddName +
"Coadd_tempExp",
551 if namespace.config.doMatchBackgrounds:
552 if namespace.config.autoReference:
553 datasetType = namespace.config.coaddName +
"Coadd"
554 validKeys = keysCoadd
556 datasetType = namespace.config.coaddName +
"Coadd_tempExp"
557 validKeys = keysCoaddTempExp
559 datasetType = namespace.config.coaddName +
"Coadd"
560 validKeys = keysCoadd
562 for dataId
in self.idList:
564 for key
in validKeys:
565 if key
not in dataId:
566 raise RuntimeError(
"--id must include " + key)
569 if (key
not in keysCoadd)
and (key
in keysCoaddTempExp):
570 if namespace.config.autoReference:
572 namespace.config.autoReference =
False
573 datasetType = namespace.config.coaddName +
"Coadd_tempExp"
574 print "Switching config.autoReference to False; applies only to background Matching."
577 dataRef = namespace.butler.dataRef(
578 datasetType = datasetType,
581 self.refList.append(dataRef)
def getBackgroundReferenceScaler
void setCoaddEdgeBits(lsst::afw::image::Mask< lsst::afw::image::MaskPixel > &coaddMask, lsst::afw::image::Image< WeightPixelT > const &weightMap)
set edge bits of coadd mask based on weight map
def addBackgroundMatchingMetadata
An integer coordinate rectangle.
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
lsst::afw::image::MaskedImage< PixelT >::Ptr statisticsStack(lsst::afw::image::MaskedImage< PixelT > const &image, Property flags, char dimension, StatisticsControl const &sctrl=StatisticsControl())
A function to compute statistics on the rows or columns of an image.