33from lsst.daf.butler
import DeferredDatasetHandle
37__all__ = [
"GetCoaddAsTemplateTask",
"GetCoaddAsTemplateConfig",
38 "GetCalexpAsTemplateTask",
"GetCalexpAsTemplateConfig",
39 "GetTemplateTask",
"GetTemplateConfig",
40 "GetDcrTemplateTask",
"GetDcrTemplateConfig",
41 "GetMultiTractCoaddTemplateTask",
"GetMultiTractCoaddTemplateConfig"]
45 templateBorderSize = pexConfig.Field(
48 doc=
"Number of pixels to grow the requested template image to account for warping"
50 coaddName = pexConfig.Field(
51 doc=
"coadd name: typically one of 'deep', 'goodSeeing', or 'dcr'",
55 warpType = pexConfig.Field(
56 doc=
"Warp type of the coadd template: one of 'direct' or 'psfMatched'",
63 """Subtask to retrieve coadd for use as an image difference template.
65 This is the default getTemplate Task to be run
as a subtask by
66 ``pipe.tasks.ImageDifferenceTask``. The main methods are ``
run()``
and
71 From the given skymap, the closest tract
is selected; multiple tracts are
72 not supported. The assembled template inherits the WCS of the selected
73 skymap tract
and the resolution of the template exposures. Overlapping box
74 regions of the input template patches are pixel by pixel copied into the
75 assembled template image. There
is no warping
or pixel resampling.
77 Pixels
with no overlap of any available input patches are set to ``nan`` value
78 and ``NO_DATA`` flagged.
81 ConfigClass = GetCoaddAsTemplateConfig
82 _DefaultName = "GetCoaddAsTemplateTask"
84 def runDataRef(self, exposure, sensorRef, templateIdList=None):
85 """Gen2 task entry point. Retrieve and mosaic a template coadd exposure
86 that overlaps the science exposure.
91 an exposure for which to generate an overlapping template
93 a Butler data reference that can be used to obtain coadd data
94 templateIdList : TYPE, optional
95 list of data ids, unused here,
in the case of coadd template
99 result : `lsst.pipe.base.Struct`
100 - ``exposure`` : `lsst.afw.image.ExposureF`
101 a template coadd exposure assembled out of patches
102 - ``sources`` :
None for this subtask
104 skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap")
105 tractInfo, patchList, skyCorners = self.
getOverlapPatchListgetOverlapPatchList(exposure, skyMap)
107 availableCoaddRefs = dict()
108 for patchInfo
in patchList:
109 patchNumber = tractInfo.getSequentialPatchIndex(patchInfo)
112 bbox=patchInfo.getOuterBBox(),
113 tract=tractInfo.getId(),
114 patch=
"%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
117 if sensorRef.datasetExists(**patchArgDict):
118 self.log.
info(
"Reading patch %s", patchArgDict)
119 availableCoaddRefs[patchNumber] = patchArgDict
121 templateExposure = self.
runrun(
122 tractInfo, patchList, skyCorners, availableCoaddRefs,
123 sensorRef=sensorRef, visitInfo=exposure.getInfo().getVisitInfo()
125 return pipeBase.Struct(exposure=templateExposure, sources=
None)
127 def runQuantum(self, exposure, butlerQC, skyMapRef, coaddExposureRefs):
128 """Gen3 task entry point. Retrieve and mosaic a template coadd exposure
129 that overlaps the science exposure.
134 The science exposure to define the sky region of the template coadd.
135 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
136 Butler like object that supports getting data by DatasetRef.
137 skyMapRef : `lsst.daf.butler.DatasetRef`
138 Reference to SkyMap object that corresponds to the template coadd.
139 coaddExposureRefs : iterable of `lsst.daf.butler.DeferredDatasetRef`
140 Iterable of references to the available template coadd patches.
144 result : `lsst.pipe.base.Struct`
145 - ``exposure`` : `lsst.afw.image.ExposureF`
146 a template coadd exposure assembled out of patches
147 - ``sources`` : `None`
for this subtask
149 self.log.warn("GetCoaddAsTemplateTask is deprecated. Use GetTemplateTask instead.")
150 skyMap = butlerQC.get(skyMapRef)
151 coaddExposureRefs = butlerQC.get(coaddExposureRefs)
152 tracts = [ref.dataId[
'tract']
for ref
in coaddExposureRefs]
153 if tracts.count(tracts[0]) == len(tracts):
154 tractInfo = skyMap[tracts[0]]
156 raise RuntimeError(
"Templates constructed from multiple Tracts not supported by this task. "
157 "Use GetTemplateTask instead.")
159 detectorBBox = exposure.getBBox()
160 detectorWcs = exposure.getWcs()
161 detectorCorners = detectorWcs.pixelToSky(
geom.Box2D(detectorBBox).getCorners())
162 validPolygon = exposure.getInfo().getValidPolygon()
163 detectorPolygon = validPolygon
if validPolygon
else geom.Box2D(detectorBBox)
165 availableCoaddRefs = dict()
167 for coaddRef
in coaddExposureRefs:
168 dataId = coaddRef.dataId
169 patchWcs = skyMap[dataId[
'tract']].getWcs()
170 patchBBox = skyMap[dataId[
'tract']][dataId[
'patch']].getOuterBBox()
171 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
173 if patchPolygon.intersection(detectorPolygon):
174 overlappingArea += patchPolygon.intersectionSingle(detectorPolygon).calculateArea()
175 if self.config.coaddName ==
'dcr':
176 self.log.
info(
"Using template input tract=%s, patch=%s, subfilter=%s",
177 dataId[
'tract'], dataId[
'patch'], dataId[
'subfilter'])
178 if dataId[
'patch']
in availableCoaddRefs:
179 availableCoaddRefs[dataId[
'patch']].
append(coaddRef)
181 availableCoaddRefs[dataId[
'patch']] = [coaddRef, ]
183 self.log.
info(
"Using template input tract=%s, patch=%s",
184 dataId[
'tract'], dataId[
'patch'])
185 availableCoaddRefs[dataId[
'patch']] = coaddRef
187 if overlappingArea == 0:
188 templateExposure =
None
190 self.log.
warning(
"No overlapping template patches found")
192 patchList = [tractInfo[patch]
for patch
in availableCoaddRefs.keys()]
193 templateExposure = self.
runrun(tractInfo, patchList, detectorCorners, availableCoaddRefs,
194 visitInfo=exposure.getInfo().getVisitInfo())
197 pixNoData = np.count_nonzero(templateExposure.mask.array
198 & templateExposure.mask.getPlaneBitMask(
'NO_DATA'))
199 pixGood = templateExposure.getBBox().getArea() - pixNoData
200 self.log.
info(
"template has %d good pixels (%.1f%%)", pixGood,
201 100*pixGood/templateExposure.getBBox().getArea())
202 return pipeBase.Struct(exposure=templateExposure, sources=
None, area=pixGood)
205 """Select the relevant tract and its patches that overlap with the science exposure.
210 The science exposure to define the sky region of the template coadd.
213 SkyMap object that corresponds to the template coadd.
221 List of all overlap patches of the selected tract.
223 Corners of the exposure in the sky
in the order given by `lsst.geom.Box2D.getCorners`.
225 expWcs = exposure.getWcs()
227 expBoxD.grow(self.config.templateBorderSize)
228 ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())
229 tractInfo = skyMap.findTract(ctrSkyPos)
230 self.log.info("Using skyMap tract %s", tractInfo.getId())
231 skyCorners = [expWcs.pixelToSky(pixPos)
for pixPos
in expBoxD.getCorners()]
232 patchList = tractInfo.findPatchList(skyCorners)
235 raise RuntimeError(
"No suitable tract found")
237 self.log.
info(
"Assembling %d coadd patches", len(patchList))
238 self.log.
info(
"exposure dimensions=%s", exposure.getDimensions())
240 return (tractInfo, patchList, skyCorners)
242 def run(self, tractInfo, patchList, skyCorners, availableCoaddRefs,
243 sensorRef=None, visitInfo=None):
244 """Gen2 and gen3 shared code: determination of exposure dimensions and
245 copying of pixels from overlapping patch regions.
250 SkyMap object that corresponds to the template coadd.
254 Patches to consider
for making the template exposure.
256 Sky corner coordinates to be covered by the template exposure.
257 availableCoaddRefs : `dict` [`int`]
258 Dictionary of spatially relevant retrieved coadd patches,
259 indexed by their sequential patch number. In Gen3 mode, values are
260 `lsst.daf.butler.DeferredDatasetHandle`
and ``.get()``
is called,
261 in Gen2 mode, ``sensorRef.get(**coaddef)``
is called to retrieve the coadd.
263 Butler data reference to get coadd data.
264 Must be `
None`
for Gen3.
266 VisitInfo to make dcr model.
270 templateExposure: `lsst.afw.image.ExposureF`
271 The created template exposure.
273 coaddWcs = tractInfo.getWcs()
277 for skyPos
in skyCorners:
278 coaddBBox.include(coaddWcs.skyToPixel(skyPos))
280 self.log.
info(
"coadd dimensions=%s", coaddBBox.getDimensions())
282 coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
283 coaddExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask(
"NO_DATA"), np.nan)
285 coaddFilterLabel =
None
287 coaddPhotoCalib =
None
288 for patchInfo
in patchList:
289 patchNumber = tractInfo.getSequentialPatchIndex(patchInfo)
290 patchSubBBox = patchInfo.getOuterBBox()
291 patchSubBBox.clip(coaddBBox)
292 if patchNumber
not in availableCoaddRefs:
293 self.log.
warning(
"skip patch=%d; patch does not exist for this coadd", patchNumber)
295 if patchSubBBox.isEmpty():
296 if isinstance(availableCoaddRefs[patchNumber], DeferredDatasetHandle):
297 tract = availableCoaddRefs[patchNumber].dataId[
'tract']
299 tract = availableCoaddRefs[patchNumber][
'tract']
300 self.log.
info(
"skip tract=%d patch=%d; no overlapping pixels", tract, patchNumber)
303 if self.config.coaddName ==
'dcr':
304 patchInnerBBox = patchInfo.getInnerBBox()
305 patchInnerBBox.clip(coaddBBox)
306 if np.min(patchInnerBBox.getDimensions()) <= 2*self.config.templateBorderSize:
307 self.log.
info(
"skip tract=%(tract)s, patch=%(patch)s; too few pixels.",
308 availableCoaddRefs[patchNumber])
310 self.log.
info(
"Constructing DCR-matched template for patch %s",
311 availableCoaddRefs[patchNumber])
313 dcrModel = DcrModel.fromQuantum(availableCoaddRefs[patchNumber],
314 self.config.effectiveWavelength,
315 self.config.bandwidth)
323 dcrBBox.grow(-self.config.templateBorderSize)
324 dcrBBox.include(patchInnerBBox)
325 coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox,
328 if sensorRef
is None:
330 coaddPatch = availableCoaddRefs[patchNumber].get()
333 coaddPatch = sensorRef.get(**availableCoaddRefs[patchNumber])
338 overlapBox = coaddPatch.getBBox()
339 overlapBox.clip(coaddBBox)
340 coaddExposure.maskedImage.assign(coaddPatch.maskedImage[overlapBox], overlapBox)
342 if coaddFilterLabel
is None:
343 coaddFilterLabel = coaddPatch.getFilter()
346 if coaddPsf
is None and coaddPatch.hasPsf():
347 coaddPsf = coaddPatch.getPsf()
350 if coaddPhotoCalib
is None:
351 coaddPhotoCalib = coaddPatch.getPhotoCalib()
353 if coaddPhotoCalib
is None:
354 raise RuntimeError(
"No coadd PhotoCalib found!")
355 if nPatchesFound == 0:
356 raise RuntimeError(
"No patches found!")
358 raise RuntimeError(
"No coadd Psf found!")
360 coaddExposure.setPhotoCalib(coaddPhotoCalib)
361 coaddExposure.setPsf(coaddPsf)
362 coaddExposure.setFilter(coaddFilterLabel)
366 """Return coadd name for given task config
370 CoaddDatasetName : `string`
372 TODO: This nearly duplicates a method in CoaddBaseTask (DM-11985)
374 warpType = self.config.warpType
375 suffix = "" if warpType ==
"direct" else warpType[0].upper() + warpType[1:]
376 return self.config.coaddName +
"Coadd" + suffix
380 doAddCalexpBackground = pexConfig.Field(
383 doc=
"Add background to calexp before processing it."
388 """Subtask to retrieve calexp of the same ccd number as the science image SensorRef
389 for use
as an image difference template. Only gen2 supported.
391 To be run
as a subtask by pipe.tasks.ImageDifferenceTask.
392 Intended
for use
with simulations
and surveys that repeatedly visit the same pointing.
393 This code was originally part of Winter2013ImageDifferenceTask.
396 ConfigClass = GetCalexpAsTemplateConfig
397 _DefaultName = "GetCalexpAsTemplateTask"
399 def run(self, exposure, sensorRef, templateIdList):
400 """Return a calexp exposure with based on input sensorRef.
402 Construct a dataId based on the sensorRef.dataId combined
403 with the specifications
from the first dataId
in templateIdList
410 Data reference of the calexp(s) to subtract
from.
412 Data reference of the template calexp to be subtraced.
413 Can be incomplete, fields are initialized
from `sensorRef`.
414 If there are multiple items, only the first one
is used.
420 return a pipeBase.Struct:
422 - ``exposure`` : a template calexp
423 - ``sources`` : source catalog measured on the template
426 if len(templateIdList) == 0:
427 raise RuntimeError(
"No template data reference supplied.")
428 if len(templateIdList) > 1:
429 self.log.
warning(
"Multiple template data references supplied. Using the first one only.")
431 templateId = sensorRef.dataId.copy()
432 templateId.update(templateIdList[0])
434 self.log.
info(
"Fetching calexp (%s) as template.", templateId)
436 butler = sensorRef.getButler()
437 template = butler.get(datasetType=
"calexp", dataId=templateId)
438 if self.config.doAddCalexpBackground:
439 templateBg = butler.get(datasetType=
"calexpBackground", dataId=templateId)
440 mi = template.getMaskedImage()
441 mi += templateBg.getImage()
443 if not template.hasPsf():
444 raise pipeBase.TaskError(
"Template has no psf")
446 templateSources = butler.get(datasetType=
"src", dataId=templateId)
447 return pipeBase.Struct(exposure=template,
448 sources=templateSources)
451 return self.
runrun(*args, **kwargs)
454 raise NotImplementedError(
"Calexp template is not supported with gen3 middleware")
458 dimensions=(
"instrument",
"visit",
"detector",
"skymap"),
459 defaultTemplates={
"coaddName":
"goodSeeing",
460 "warpTypeSuffix":
"",
462 bbox = pipeBase.connectionTypes.Input(
463 doc=
"BBoxes of calexp used determine geometry of output template",
464 name=
"{fakesType}calexp.bbox",
465 storageClass=
"Box2I",
466 dimensions=(
"instrument",
"visit",
"detector"),
468 wcs = pipeBase.connectionTypes.Input(
469 doc=
"WCS of the calexp that we want to fetch the template for",
470 name=
"{fakesType}calexp.wcs",
472 dimensions=(
"instrument",
"visit",
"detector"),
474 skyMap = pipeBase.connectionTypes.Input(
475 doc=
"Input definition of geometry/bbox and projection/wcs for template exposures",
476 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
477 dimensions=(
"skymap", ),
478 storageClass=
"SkyMap",
482 coaddExposures = pipeBase.connectionTypes.Input(
483 doc=
"Input template to match and subtract from the exposure",
484 dimensions=(
"tract",
"patch",
"skymap",
"band"),
485 storageClass=
"ExposureF",
486 name=
"{fakesType}{coaddName}Coadd{warpTypeSuffix}",
490 outputExposure = pipeBase.connectionTypes.Output(
491 doc=
"Warped template used to create `subtractedExposure`.",
492 dimensions=(
"instrument",
"visit",
"detector"),
493 storageClass=
"ExposureF",
494 name=
"{fakesType}{coaddName}Diff_templateExp{warpTypeSuffix}",
498class GetTemplateConfig(pipeBase.PipelineTaskConfig,
499 pipelineConnections=GetTemplateConnections):
500 templateBorderSize = pexConfig.Field(
503 doc=
"Number of pixels to grow the requested template image to account for warping"
505 warp = pexConfig.ConfigField(
506 dtype=afwMath.Warper.ConfigClass,
507 doc=
"warper configuration",
509 coaddPsf = pexConfig.ConfigField(
510 doc=
"Configuration for CoaddPsf",
511 dtype=CoaddPsfConfig,
515 self.warp.warpingKernelName =
'lanczos5'
516 self.coaddPsf.warpingKernelName =
'lanczos5'
519class GetTemplateTask(pipeBase.PipelineTask):
520 ConfigClass = GetTemplateConfig
521 _DefaultName =
"getTemplate"
523 def __init__(self, *args, **kwargs):
524 super().__init__(*args, **kwargs)
525 self.warper = afwMath.Warper.fromConfig(self.config.warp)
527 def runQuantum(self, butlerQC, inputRefs, outputRefs):
529 inputs = butlerQC.get(inputRefs)
530 results = self.getOverlappingExposures(inputs)
531 inputs[
"coaddExposures"] = results.coaddExposures
532 inputs[
"dataIds"] = results.dataIds
533 outputs = self.run(**inputs)
534 butlerQC.put(outputs, outputRefs)
536 def getOverlappingExposures(self, inputs):
537 """Return lists of coadds and their corresponding dataIds that overlap the detector.
539 The spatial index in the registry has generous padding
and often supplies
540 patches near, but
not directly overlapping the detector.
541 Filters inputs so that we don
't have to read in all input coadds.
545 inputs : `dict` of task Inputs, containing:
546 - coaddExposureRefs : list of elements of type
547 `lsst.daf.butler.DeferredDatasetHandle` of
549 Data references to exposures that might overlap the detector.
551 Template Bounding box of the detector geometry onto which to
552 resample the coaddExposures
553 - skyMap : `lsst.skymap.SkyMap`
554 Input definition of geometry/bbox and projection/wcs
for template exposures
556 Template WCS onto which to resample the coaddExposures
560 result : `lsst.pipe.base.Struct` containing these fields:
562 Coadd exposures that overlap the detector.
563 - dataIds : `list` of `lsst.daf.butler.DataCoordinate`
564 Data IDs of the coadd exposures that overlap the detector.
569 Raised
if no patches overlap the input detector bbox
575 coaddExposureList = []
577 for coaddRef
in inputs[
'coaddExposures']:
578 dataId = coaddRef.dataId
579 patchWcs = inputs[
'skyMap'][dataId[
'tract']].getWcs()
580 patchBBox = inputs[
'skyMap'][dataId[
'tract']][dataId[
'patch']].getOuterBBox()
581 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
583 if patchPolygon.intersection(detectorPolygon):
584 overlappingArea += patchPolygon.intersectionSingle(detectorPolygon).calculateArea()
585 self.log.
info(
"Using template input tract=%s, patch=%s" %
586 (dataId[
'tract'], dataId[
'patch']))
587 coaddExposureList.append(coaddRef.get())
588 dataIds.append(dataId)
590 if not overlappingArea:
591 raise pipeBase.NoWorkFound(
'No patches overlap detector')
593 return pipeBase.Struct(coaddExposures=coaddExposureList,
596 def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs):
597 """Warp coadds from multiple tracts to form a template for image diff.
599 Where the tracts overlap, the resulting template image is averaged.
600 The PSF on the template
is created by combining the CoaddPsf on each
601 template image into a meta-CoaddPsf.
606 Coadds to be mosaicked
608 Template Bounding box of the detector geometry onto which to
609 resample the coaddExposures
611 Template WCS onto which to resample the coaddExposures
612 dataIds : `list` of `lsst.daf.butler.DataCoordinate`
613 Record of the tract
and patch of each coaddExposure.
615 Any additional keyword parameters.
619 result : `lsst.pipe.base.Struct` containing
620 - ``outputExposure`` : a template coadd exposure assembled out of patches
623 tractsSchema = afwTable.ExposureTable.makeMinimalSchema()
624 tractKey = tractsSchema.addField(
'tract', type=np.int32, doc=
'Which tract')
625 patchKey = tractsSchema.addField(
'patch', type=np.int32, doc=
'Which patch')
626 weightKey = tractsSchema.addField(
'weight', type=float, doc=
'Weight for each tract, should be 1')
630 bbox.grow(self.config.templateBorderSize)
637 for coaddExposure, dataId
in zip(coaddExposures, dataIds):
640 warped = self.warper.
warpExposure(finalWcs, coaddExposure, maxBBox=finalBBox)
643 if not np.any(np.isfinite(warped.image.array)):
644 self.log.
info(
"No overlap for warped %s. Skipping" % dataId)
647 exp = afwImage.ExposureF(finalBBox, finalWcs)
648 exp.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask(
"NO_DATA"), np.nan)
649 exp.maskedImage.assign(warped.maskedImage, warped.getBBox())
651 maskedImageList.append(exp.maskedImage)
653 record = tractsCatalog.addNew()
654 record.setPsf(coaddExposure.getPsf())
655 record.setWcs(coaddExposure.getWcs())
656 record.setPhotoCalib(coaddExposure.getPhotoCalib())
657 record.setBBox(coaddExposure.getBBox())
659 record.set(tractKey, dataId[
'tract'])
660 record.set(patchKey, dataId[
'patch'])
661 record.set(weightKey, 1.)
664 if nPatchesFound == 0:
665 raise pipeBase.NoWorkFound(
"No patches found to overlap detector")
670 statsCtrl.setNanSafe(
True)
671 statsCtrl.setWeighted(
True)
672 statsCtrl.setCalcErrorFromInputVariance(
True)
674 templateExposure = afwImage.ExposureF(finalBBox, finalWcs)
675 templateExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask(
"NO_DATA"), np.nan)
676 xy0 = templateExposure.getXY0()
679 weightList, clipped=0, maskMap=[])
680 templateExposure.maskedImage.setXY0(xy0)
684 boolmask = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
'NO_DATA') == 0
686 centerCoord = afwGeom.SpanSet.fromMask(maskx, 1).computeCentroid()
688 ctrl = self.config.coaddPsf.makeControl()
689 coaddPsf =
CoaddPsf(tractsCatalog, finalWcs, centerCoord, ctrl.warpingKernelName, ctrl.cacheSize)
691 raise RuntimeError(
"CoaddPsf could not be constructed")
693 templateExposure.setPsf(coaddPsf)
694 templateExposure.setFilter(coaddExposure.getFilter())
695 templateExposure.setPhotoCalib(coaddExposure.getPhotoCalib())
696 return pipeBase.Struct(outputExposure=templateExposure)
700 dimensions=(
"instrument",
"visit",
"detector",
"skymap"),
701 defaultTemplates={
"coaddName":
"dcr",
702 "warpTypeSuffix":
"",
704 visitInfo = pipeBase.connectionTypes.Input(
705 doc=
"VisitInfo of calexp used to determine observing conditions.",
706 name=
"{fakesType}calexp.visitInfo",
707 storageClass=
"VisitInfo",
708 dimensions=(
"instrument",
"visit",
"detector"),
710 dcrCoadds = pipeBase.connectionTypes.Input(
711 doc=
"Input DCR template to match and subtract from the exposure",
712 name=
"{fakesType}dcrCoadd{warpTypeSuffix}",
713 storageClass=
"ExposureF",
714 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
719 def __init__(self, *, config=None):
720 super().__init__(config=config)
721 self.inputs.remove(
"coaddExposures")
724class GetDcrTemplateConfig(GetTemplateConfig,
725 pipelineConnections=GetDcrTemplateConnections):
726 numSubfilters = pexConfig.Field(
727 doc=
"Number of subfilters in the DcrCoadd.",
731 effectiveWavelength = pexConfig.Field(
732 doc=
"Effective wavelength of the filter.",
736 bandwidth = pexConfig.Field(
737 doc=
"Bandwidth of the physical filter.",
743 if self.effectiveWavelength
is None or self.bandwidth
is None:
744 raise ValueError(
"The effective wavelength and bandwidth of the physical filter "
745 "must be set in the getTemplate config for DCR coadds. "
746 "Required until transmission curves are used in DM-13668.")
749class GetDcrTemplateTask(GetTemplateTask):
750 ConfigClass = GetDcrTemplateConfig
751 _DefaultName =
"getDcrTemplate"
753 def getOverlappingExposures(self, inputs):
754 """Return lists of coadds and their corresponding dataIds that overlap the detector.
756 The spatial index in the registry has generous padding
and often supplies
757 patches near, but
not directly overlapping the detector.
758 Filters inputs so that we don
't have to read in all input coadds.
762 inputs : `dict` of task Inputs, containing:
763 - coaddExposureRefs : `list` of elements of type
764 `lsst.daf.butler.DeferredDatasetHandle` of
766 Data references to exposures that might overlap the detector.
768 Template Bounding box of the detector geometry onto which to
769 resample the coaddExposures
770 - skyMap : `lsst.skymap.SkyMap`
771 Input definition of geometry/bbox and projection/wcs
for template exposures
773 Template WCS onto which to resample the coaddExposures
775 Metadata
for the science image.
779 result : `lsst.pipe.base.Struct` containing these fields:
781 Coadd exposures that overlap the detector.
782 - dataIds : `list` of `lsst.daf.butler.DataCoordinate`
783 Data IDs of the coadd exposures that overlap the detector.
788 Raised
if no patches overlatp the input detector bbox
794 coaddExposureRefList = []
797 for coaddRef
in inputs[
"dcrCoadds"]:
798 dataId = coaddRef.dataId
799 patchWcs = inputs[
"skyMap"][dataId[
'tract']].getWcs()
800 patchBBox = inputs[
"skyMap"][dataId[
'tract']][dataId[
'patch']].getOuterBBox()
801 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
803 if patchPolygon.intersection(detectorPolygon):
804 overlappingArea += patchPolygon.intersectionSingle(detectorPolygon).calculateArea()
805 self.log.
info(
"Using template input tract=%s, patch=%s, subfilter=%s" %
806 (dataId[
'tract'], dataId[
'patch'], dataId[
"subfilter"]))
807 coaddExposureRefList.append(coaddRef)
808 if dataId[
'tract']
in patchList:
809 patchList[dataId[
'tract']].
append(dataId[
'patch'])
811 patchList[dataId[
'tract']] = [dataId[
'patch'], ]
812 dataIds.append(dataId)
814 if not overlappingArea:
815 raise pipeBase.NoWorkFound(
'No patches overlap detector')
817 self.checkPatchList(patchList)
819 coaddExposures = self.getDcrModel(patchList, inputs[
'dcrCoadds'], inputs[
'visitInfo'])
820 return pipeBase.Struct(coaddExposures=coaddExposures,
824 """Check that all of the DcrModel subfilters are present for each patch.
829 Dict of the patches containing valid data for each tract
834 If the number of exposures found
for a patch does
not match the number of subfilters.
836 for tract
in patchList:
837 for patch
in set(patchList[tract]):
838 if patchList[tract].count(patch) != self.config.numSubfilters:
839 raise RuntimeError(
"Invalid number of DcrModel subfilters found: %d vs %d expected",
840 patchList[tract].count(patch), self.config.numSubfilters)
843 """Build DCR-matched coadds from a list of exposure references.
848 Dict of the patches containing valid data for each tract
849 coaddRefs : `list` of elements of type
850 `lsst.daf.butler.DeferredDatasetHandle` of
852 Data references to DcrModels that overlap the detector.
854 Metadata
for the science image.
859 Coadd exposures that overlap the detector.
861 coaddExposureList = []
862 for tract
in patchList:
863 for patch
in set(patchList[tract]):
864 coaddRefList = [coaddRef
for coaddRef
in coaddRefs
865 if _selectDataRef(coaddRef, tract, patch)]
867 dcrModel = DcrModel.fromQuantum(coaddRefList,
868 self.config.effectiveWavelength,
869 self.config.bandwidth,
870 self.config.numSubfilters)
871 coaddExposureList.append(dcrModel.buildMatchedExposure(visitInfo=visitInfo))
872 return coaddExposureList
875def _selectDataRef(coaddRef, tract, patch):
876 condition = (coaddRef.dataId[
'tract'] == tract) & (coaddRef.dataId[
'patch'] == patch)
884class GetMultiTractCoaddTemplateTask(GetTemplateTask):
885 ConfigClass = GetMultiTractCoaddTemplateConfig
886 _DefaultName =
"getMultiTractCoaddTemplate"
890 self.log.
warn(
"GetMultiTractCoaddTemplateTask is deprecated. Use GetTemplateTask instead.")
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Information about a single exposure of an imaging camera.
Pass parameters to a Statistics object.
Custom catalog class for ExposureRecord/Table.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
def runDataRef(self, *args, **kwargs)
def runQuantum(self, **kwargs)
def run(self, exposure, sensorRef, templateIdList)
def runDataRef(self, exposure, sensorRef, templateIdList=None)
def getCoaddDatasetName(self)
def getOverlapPatchList(self, exposure, skyMap)
def run(self, tractInfo, patchList, skyCorners, availableCoaddRefs, sensorRef=None, visitInfo=None)
def runQuantum(self, exposure, butlerQC, skyMapRef, coaddExposureRefs)
def __init__(self, *args, **kwargs)
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
daf::base::PropertySet * set
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
std::shared_ptr< lsst::afw::image::Image< PixelT > > statisticsStack(std::vector< std::shared_ptr< lsst::afw::image::Image< PixelT > > > &images, Property flags, StatisticsControl const &sctrl=StatisticsControl(), std::vector< lsst::afw::image::VariancePixel > const &wvector=std::vector< lsst::afw::image::VariancePixel >(0))
A function to compute some statistics of a stack of Images.
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)
int warpExposure(DestExposureT &destExposure, SrcExposureT const &srcExposure, WarpingControl const &control, typename DestExposureT::MaskedImageT::SinglePixel padValue=lsst::afw::math::edgePixel< typename DestExposureT::MaskedImageT >(typename lsst::afw::image::detail::image_traits< typename DestExposureT::MaskedImageT >::image_category()))
Warp (remap) one exposure to another.
def checkPatchList(self, patchList)
def getDcrModel(self, patchList, coaddRefs, visitInfo)
def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.