warpAndPsfMatch = pexConfig.ConfigurableField(
target=WarpAndPsfMatchTask,
doc="Task to warp and PSF-match calexp",
)
doWrite = pexConfig.Field(
doc="persist <coaddName>Coadd_<warpType>Warp",
dtype=bool,
default=True,
)
bgSubtracted = pexConfig.Field(
doc="Work with a background subtracted calexp?",
dtype=bool,
default=True,
)
coaddPsf = pexConfig.ConfigField(
doc="Configuration for CoaddPsf",
dtype=CoaddPsfConfig,
)
makeDirect = pexConfig.Field(
doc="Make direct Warp/Coadds",
dtype=bool,
default=True,
)
makePsfMatched = pexConfig.Field(
doc="Make Psf-Matched Warp/Coadd?",
dtype=bool,
default=False,
)
doWriteEmptyWarps = pexConfig.Field(
dtype=bool,
default=False,
doc="Write out warps even if they are empty"
)
hasFakes = pexConfig.Field(
doc="Should be set to True if fake sources have been inserted into the input data.",
dtype=bool,
default=False,
)
doApplySkyCorr = pexConfig.Field(
dtype=bool,
default=False,
doc="Apply sky correction?",
)
doApplyFinalizedPsf = pexConfig.Field(
doc="Whether to apply finalized psf models and aperture correction map.",
dtype=bool,
default=True,
)
def validate(self):
CoaddBaseTask.ConfigClass.validate(self)
if not self.makePsfMatched and not self.makeDirect:
raise RuntimeError("At least one of config.makePsfMatched and config.makeDirect must be True")
if self.doPsfMatch:
# Backwards compatibility.
log.warning("Config doPsfMatch deprecated. Setting makePsfMatched=True and makeDirect=False")
self.makePsfMatched = True
self.makeDirect = False
def setDefaults(self):
CoaddBaseTask.ConfigClass.setDefaults(self)
self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize = self.matchingKernelSize
class MakeWarpTask(CoaddBaseTask):
ConfigClass = MakeWarpConfig
_DefaultName = "makeWarp"
def __init__(self, **kwargs):
CoaddBaseTask.__init__(self, **kwargs)
self.makeSubtask("warpAndPsfMatch")
if self.config.hasFakes:
self.calexpType = "fakes_calexp"
else:
self.calexpType = "calexp"
@utils.inheritDoc(pipeBase.PipelineTask)
def runQuantum(self, butlerQC, inputRefs, outputRefs):
# Obtain the list of input detectors from calExpList. Sort them by
# detector order (to ensure reproducibility). Then ensure all input
# lists are in the same sorted detector order.
detectorOrder = [ref.datasetRef.dataId['detector'] for ref in inputRefs.calExpList]
detectorOrder.sort()
inputRefs = reorderRefs(inputRefs, detectorOrder, dataIdKey='detector')
# Read in all inputs.
inputs = butlerQC.get(inputRefs)
# Construct skyInfo expected by `run`. We remove the SkyMap itself
# from the dictionary so we can pass it as kwargs later.
skyMap = inputs.pop("skyMap")
quantumDataId = butlerQC.quantum.dataId
skyInfo = makeSkyInfo(skyMap, tractId=quantumDataId['tract'], patchId=quantumDataId['patch'])
# Construct list of input DataIds expected by `run`
dataIdList = [ref.datasetRef.dataId for ref in inputRefs.calExpList]
# Construct list of packed integer IDs expected by `run`
ccdIdList = [dataId.pack("visit_detector") for dataId in dataIdList]
# Run the selector and filter out calexps that were not selected
# primarily because they do not overlap the patch
cornerPosList = lsst.geom.Box2D(skyInfo.bbox).getCorners()
coordList = [skyInfo.wcs.pixelToSky(pos) for pos in cornerPosList]
goodIndices = self.select.run(**inputs, coordList=coordList, dataIds=dataIdList)
inputs = self.filterInputs(indices=goodIndices, inputs=inputs)
# Read from disk only the selected calexps
inputs['calExpList'] = [ref.get() for ref in inputs['calExpList']]
# Extract integer visitId requested by `run`
visits = [dataId['visit'] for dataId in dataIdList]
visitId = visits[0]
if self.config.doApplyExternalSkyWcs:
if self.config.useGlobalExternalSkyWcs:
externalSkyWcsCatalog = inputs.pop("externalSkyWcsGlobalCatalog")
else:
externalSkyWcsCatalog = inputs.pop("externalSkyWcsTractCatalog")
else:
externalSkyWcsCatalog = None
if self.config.doApplyExternalPhotoCalib:
if self.config.useGlobalExternalPhotoCalib:
externalPhotoCalibCatalog = inputs.pop("externalPhotoCalibGlobalCatalog")
else:
externalPhotoCalibCatalog = inputs.pop("externalPhotoCalibTractCatalog")
else:
externalPhotoCalibCatalog = None
if self.config.doApplyFinalizedPsf:
finalizedPsfApCorrCatalog = inputs.pop("finalizedPsfApCorrCatalog")
else:
finalizedPsfApCorrCatalog = None
completeIndices = self.prepareCalibratedExposures(**inputs,
externalSkyWcsCatalog=externalSkyWcsCatalog,
externalPhotoCalibCatalog=externalPhotoCalibCatalog,
finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog)
# Redo the input selection with inputs with complete wcs/photocalib info.
inputs = self.filterInputs(indices=completeIndices, inputs=inputs)
results = self.run(**inputs, visitId=visitId,
ccdIdList=[ccdIdList[i] for i in goodIndices],
dataIdList=[dataIdList[i] for i in goodIndices],
skyInfo=skyInfo)
if self.config.makeDirect and results.exposures["direct"] is not None:
butlerQC.put(results.exposures["direct"], outputRefs.direct)
if self.config.makePsfMatched and results.exposures["psfMatched"] is not None:
butlerQC.put(results.exposures["psfMatched"], outputRefs.psfMatched)
@timeMethod
def run(self, calExpList, ccdIdList, skyInfo, visitId=0, dataIdList=None, **kwargs):
warpTypeList = self.getWarpTypeList()
totGoodPix = {warpType: 0 for warpType in warpTypeList}
didSetMetadata = {warpType: False for warpType in warpTypeList}
warps = {warpType: self._prepareEmptyExposure(skyInfo) for warpType in warpTypeList}
inputRecorder = {warpType: self.inputRecorder.makeCoaddTempExpRecorder(visitId, len(calExpList))
for warpType in warpTypeList}
modelPsf = self.config.modelPsf.apply() if self.config.makePsfMatched else None
if dataIdList is None:
dataIdList = ccdIdList
for calExpInd, (calExp, ccdId, dataId) in enumerate(zip(calExpList, ccdIdList, dataIdList)):
self.log.info("Processing calexp %d of %d for this Warp: id=%s",
calExpInd+1, len(calExpList), dataId)
try:
warpedAndMatched = self.warpAndPsfMatch.run(calExp, modelPsf=modelPsf,
wcs=skyInfo.wcs, maxBBox=skyInfo.bbox,
makeDirect=self.config.makeDirect,
makePsfMatched=self.config.makePsfMatched)
except Exception as e:
self.log.warning("WarpAndPsfMatch failed for calexp %s; skipping it: %s", dataId, e)
continue
try:
numGoodPix = {warpType: 0 for warpType in warpTypeList}
for warpType in warpTypeList:
exposure = warpedAndMatched.getDict()[warpType]
if exposure is None:
continue
warp = warps[warpType]
if didSetMetadata[warpType]:
mimg = exposure.getMaskedImage()
mimg *= (warp.getPhotoCalib().getInstFluxAtZeroMagnitude()
/ exposure.getPhotoCalib().getInstFluxAtZeroMagnitude())
del mimg
numGoodPix[warpType] = coaddUtils.copyGoodPixels(
warp.getMaskedImage(), exposure.getMaskedImage(), self.getBadPixelMask())
totGoodPix[warpType] += numGoodPix[warpType]
self.log.debug("Calexp %s has %d good pixels in this patch (%.1f%%) for %s",
dataId, numGoodPix[warpType],
100.0*numGoodPix[warpType]/skyInfo.bbox.getArea(), warpType)
if numGoodPix[warpType] > 0 and not didSetMetadata[warpType]:
warp.info.id = exposure.info.id
warp.setPhotoCalib(exposure.getPhotoCalib())
warp.setFilter(exposure.getFilter())
warp.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
# PSF replaced with CoaddPsf after loop if and only if creating direct warp
warp.setPsf(exposure.getPsf())
didSetMetadata[warpType] = True
# Need inputRecorder for CoaddApCorrMap for both direct and PSF-matched
inputRecorder[warpType].addCalExp(calExp, ccdId, numGoodPix[warpType])
except Exception as e:
self.log.warning("Error processing calexp %s; skipping it: %s", dataId, e)
continue
for warpType in warpTypeList:
self.log.info("%sWarp has %d good pixels (%.1f%%)",
warpType, totGoodPix[warpType], 100.0*totGoodPix[warpType]/skyInfo.bbox.getArea())
if totGoodPix[warpType] > 0 and didSetMetadata[warpType]:
inputRecorder[warpType].finish(warps[warpType], totGoodPix[warpType])
if warpType == "direct":
warps[warpType].setPsf(
CoaddPsf(inputRecorder[warpType].coaddInputs.ccds, skyInfo.wcs,
self.config.coaddPsf.makeControl()))
else:
if not self.config.doWriteEmptyWarps:
# No good pixels. Exposure still empty
warps[warpType] = None
# NoWorkFound is unnecessary as the downstream tasks will
# adjust the quantum accordingly.
result = pipeBase.Struct(exposures=warps)
return result
def filterInputs(self, indices, inputs):
for key in inputs.keys():
# Only down-select on list inputs
if isinstance(inputs[key], list):
inputs[key] = [inputs[key][ind] for ind in indices]
return inputs
def prepareCalibratedExposures(self, calExpList, backgroundList=None, skyCorrList=None,
externalSkyWcsCatalog=None, externalPhotoCalibCatalog=None,
finalizedPsfApCorrCatalog=None,
**kwargs):
Definition at line 59 of file makeWarp.py.