34__all__ = [
"GetTemplateTask",
"GetTemplateConfig",
35 "GetDcrTemplateTask",
"GetDcrTemplateConfig"]
39 dimensions=(
"instrument",
"visit",
"detector",
"skymap"),
40 defaultTemplates={
"coaddName":
"goodSeeing",
43 bbox = pipeBase.connectionTypes.Input(
44 doc=
"BBoxes of calexp used determine geometry of output template",
45 name=
"{fakesType}calexp.bbox",
47 dimensions=(
"instrument",
"visit",
"detector"),
49 wcs = pipeBase.connectionTypes.Input(
50 doc=
"WCS of the calexp that we want to fetch the template for",
51 name=
"{fakesType}calexp.wcs",
53 dimensions=(
"instrument",
"visit",
"detector"),
55 skyMap = pipeBase.connectionTypes.Input(
56 doc=
"Input definition of geometry/bbox and projection/wcs for template exposures",
57 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
58 dimensions=(
"skymap", ),
59 storageClass=
"SkyMap",
63 coaddExposures = pipeBase.connectionTypes.Input(
64 doc=
"Input template to match and subtract from the exposure",
65 dimensions=(
"tract",
"patch",
"skymap",
"band"),
66 storageClass=
"ExposureF",
67 name=
"{fakesType}{coaddName}Coadd{warpTypeSuffix}",
71 template = pipeBase.connectionTypes.Output(
72 doc=
"Warped template used to create `subtractedExposure`.",
73 dimensions=(
"instrument",
"visit",
"detector"),
74 storageClass=
"ExposureF",
75 name=
"{fakesType}{coaddName}Diff_templateExp{warpTypeSuffix}",
79class GetTemplateConfig(pipeBase.PipelineTaskConfig,
80 pipelineConnections=GetTemplateConnections):
81 templateBorderSize = pexConfig.Field(
84 doc=
"Number of pixels to grow the requested template image to account for warping"
86 warp = pexConfig.ConfigField(
87 dtype=afwMath.Warper.ConfigClass,
88 doc=
"warper configuration",
90 coaddPsf = pexConfig.ConfigField(
91 doc=
"Configuration for CoaddPsf",
95 def setDefaults(self):
96 self.warp.warpingKernelName =
'lanczos5'
97 self.coaddPsf.warpingKernelName =
'lanczos5'
100class GetTemplateTask(pipeBase.PipelineTask):
101 ConfigClass = GetTemplateConfig
102 _DefaultName =
"getTemplate"
104 def __init__(self, *args, **kwargs):
105 super().__init__(*args, **kwargs)
106 self.warper = afwMath.Warper.fromConfig(self.config.warp)
108 def runQuantum(self, butlerQC, inputRefs, outputRefs):
110 inputs = butlerQC.get(inputRefs)
111 results = self.getOverlappingExposures(inputs)
112 inputs[
"coaddExposures"] = results.coaddExposures
113 inputs[
"dataIds"] = results.dataIds
114 inputs[
"physical_filter"] = butlerQC.quantum.dataId[
"physical_filter"]
115 outputs = self.run(**inputs)
116 butlerQC.put(outputs, outputRefs)
118 def getOverlappingExposures(self, inputs):
119 """Return lists of coadds and their corresponding dataIds that overlap
122 The spatial index in the registry has generous padding
and often
123 supplies patches near, but
not directly overlapping the detector.
124 Filters inputs so that we don
't have to read in all input coadds.
128 inputs : `dict` of task Inputs, containing:
129 - coaddExposureRefs : `list`
130 [`lsst.daf.butler.DeferredDatasetHandle` of
132 Data references to exposures that might overlap the detector.
134 Template Bounding box of the detector geometry onto which to
135 resample the coaddExposures.
136 - skyMap : `lsst.skymap.SkyMap`
137 Input definition of geometry/bbox and projection/wcs
for
140 Template WCS onto which to resample the coaddExposures.
144 result : `lsst.pipe.base.Struct`
145 A struct
with attributes:
148 List of Coadd exposures that overlap the detector (`list`
151 List of data IDs of the coadd exposures that overlap the
152 detector (`list` [`lsst.daf.butler.DataCoordinate`]).
157 Raised
if no patches overlap the input detector bbox.
163 coaddExposureList = []
165 for coaddRef
in inputs[
'coaddExposures']:
166 dataId = coaddRef.dataId
167 patchWcs = inputs[
'skyMap'][dataId[
'tract']].getWcs()
168 patchBBox = inputs[
'skyMap'][dataId[
'tract']][dataId[
'patch']].getOuterBBox()
169 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
170 inputsWcs = inputs[
'wcs']
171 if inputsWcs
is not None:
173 if patchPolygon.intersection(detectorPolygon):
174 overlappingArea += patchPolygon.intersectionSingle(detectorPolygon).calculateArea()
175 self.log.info(
"Using template input tract=%s, patch=%s" %
176 (dataId[
'tract'], dataId[
'patch']))
177 coaddExposureList.append(coaddRef.get())
178 dataIds.append(dataId)
180 self.log.info(
"Exposure has no WCS, so cannot create associated template.")
182 if not overlappingArea:
183 raise pipeBase.NoWorkFound(
'No patches overlap detector')
185 return pipeBase.Struct(coaddExposures=coaddExposureList,
188 def run(self, coaddExposures, bbox, wcs, dataIds, physical_filter=None, **kwargs):
189 """Warp coadds from multiple tracts to form a template for image diff.
191 Where the tracts overlap, the resulting template image is averaged.
192 The PSF on the template
is created by combining the CoaddPsf on each
193 template image into a meta-CoaddPsf.
198 Coadds to be mosaicked.
200 Template Bounding box of the detector geometry onto which to
201 resample the ``coaddExposures``.
203 Template WCS onto which to resample the ``coaddExposures``.
204 dataIds : `list` [`lsst.daf.butler.DataCoordinate`]
205 Record of the tract
and patch of each coaddExposure.
206 physical_filter : `str`, optional
207 The physical filter of the science image.
209 Any additional keyword parameters.
213 result : `lsst.pipe.base.Struct`
214 A struct
with attributes:
217 A template coadd exposure assembled out of patches
218 (`lsst.afw.image.ExposureF`).
223 If no coadds are found
with sufficient un-masked pixels.
225 If the PSF of the template can
't be calculated.
228 tractsSchema = afwTable.ExposureTable.makeMinimalSchema()
229 tractKey = tractsSchema.addField(
'tract', type=np.int32, doc=
'Which tract')
230 patchKey = tractsSchema.addField(
'patch', type=np.int32, doc=
'Which patch')
231 weightKey = tractsSchema.addField(
'weight', type=float, doc=
'Weight for each tract, should be 1')
235 bbox.grow(self.config.templateBorderSize)
242 for coaddExposure, dataId
in zip(coaddExposures, dataIds):
245 warped = self.warper.warpExposure(finalWcs, coaddExposure, maxBBox=finalBBox)
248 if not np.any(np.isfinite(warped.image.array)):
249 self.log.info(
"No overlap for warped %s. Skipping" % dataId)
252 exp = afwImage.ExposureF(finalBBox, finalWcs)
253 exp.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask(
"NO_DATA"), np.nan)
254 exp.maskedImage.assign(warped.maskedImage, warped.getBBox())
256 maskedImageList.append(exp.maskedImage)
258 record = tractsCatalog.addNew()
259 record.setPsf(coaddExposure.getPsf())
260 record.setWcs(coaddExposure.getWcs())
261 record.setPhotoCalib(coaddExposure.getPhotoCalib())
262 record.setBBox(coaddExposure.getBBox())
264 record.set(tractKey, dataId[
'tract'])
265 record.set(patchKey, dataId[
'patch'])
266 record.set(weightKey, 1.)
269 if nPatchesFound == 0:
270 raise pipeBase.NoWorkFound(
"No patches found to overlap detector")
275 statsCtrl.setNanSafe(
True)
276 statsCtrl.setWeighted(
True)
277 statsCtrl.setCalcErrorMosaicMode(
True)
279 templateExposure = afwImage.ExposureF(finalBBox, finalWcs)
280 templateExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask(
"NO_DATA"), np.nan)
281 xy0 = templateExposure.getXY0()
284 weightList, clipped=0, maskMap=[])
285 templateExposure.maskedImage.setXY0(xy0)
289 boolmask = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
'NO_DATA') == 0
291 centerCoord = afwGeom.SpanSet.fromMask(maskx, 1).computeCentroid()
293 ctrl = self.config.coaddPsf.makeControl()
294 coaddPsf =
CoaddPsf(tractsCatalog, finalWcs, centerCoord, ctrl.warpingKernelName, ctrl.cacheSize)
296 raise RuntimeError(
"CoaddPsf could not be constructed")
298 templateExposure.setPsf(coaddPsf)
300 if physical_filter
is None:
301 filterLabel = coaddExposure.getFilter()
304 templateExposure.setFilter(filterLabel)
305 templateExposure.setPhotoCalib(coaddExposure.getPhotoCalib())
306 return pipeBase.Struct(template=templateExposure)
310 dimensions=(
"instrument",
"visit",
"detector",
"skymap"),
311 defaultTemplates={
"coaddName":
"dcr",
312 "warpTypeSuffix":
"",
314 visitInfo = pipeBase.connectionTypes.Input(
315 doc=
"VisitInfo of calexp used to determine observing conditions.",
316 name=
"{fakesType}calexp.visitInfo",
317 storageClass=
"VisitInfo",
318 dimensions=(
"instrument",
"visit",
"detector"),
320 dcrCoadds = pipeBase.connectionTypes.Input(
321 doc=
"Input DCR template to match and subtract from the exposure",
322 name=
"{fakesType}dcrCoadd{warpTypeSuffix}",
323 storageClass=
"ExposureF",
324 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
329 def __init__(self, *, config=None):
330 super().__init__(config=config)
331 self.inputs.remove(
"coaddExposures")
334class GetDcrTemplateConfig(GetTemplateConfig,
335 pipelineConnections=GetDcrTemplateConnections):
336 numSubfilters = pexConfig.Field(
337 doc=
"Number of subfilters in the DcrCoadd.",
341 effectiveWavelength = pexConfig.Field(
342 doc=
"Effective wavelength of the filter.",
346 bandwidth = pexConfig.Field(
347 doc=
"Bandwidth of the physical filter.",
353 if self.effectiveWavelength
is None or self.bandwidth
is None:
354 raise ValueError(
"The effective wavelength and bandwidth of the physical filter "
355 "must be set in the getTemplate config for DCR coadds. "
356 "Required until transmission curves are used in DM-13668.")
359class GetDcrTemplateTask(GetTemplateTask):
360 ConfigClass = GetDcrTemplateConfig
361 _DefaultName =
"getDcrTemplate"
363 def getOverlappingExposures(self, inputs):
364 """Return lists of coadds and their corresponding dataIds that overlap
367 The spatial index in the registry has generous padding
and often
368 supplies patches near, but
not directly overlapping the detector.
369 Filters inputs so that we don
't have to read in all input coadds.
373 inputs : `dict` of task Inputs, containing:
374 - coaddExposureRefs : `list`
375 [`lsst.daf.butler.DeferredDatasetHandle` of
377 Data references to exposures that might overlap the detector.
379 Template Bounding box of the detector geometry onto which to
380 resample the coaddExposures.
381 - skyMap : `lsst.skymap.SkyMap`
382 Input definition of geometry/bbox and projection/wcs
for
385 Template WCS onto which to resample the coaddExposures.
387 Metadata
for the science image.
391 result : `lsst.pipe.base.Struct`
392 A struct
with attibutes:
395 Coadd exposures that overlap the detector (`list`
398 Data IDs of the coadd exposures that overlap the detector
399 (`list` [`lsst.daf.butler.DataCoordinate`]).
404 Raised
if no patches overlatp the input detector bbox.
410 coaddExposureRefList = []
413 for coaddRef
in inputs[
"dcrCoadds"]:
414 dataId = coaddRef.dataId
415 patchWcs = inputs[
"skyMap"][dataId[
'tract']].getWcs()
416 patchBBox = inputs[
"skyMap"][dataId[
'tract']][dataId[
'patch']].getOuterBBox()
417 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
419 if patchPolygon.intersection(detectorPolygon):
420 overlappingArea += patchPolygon.intersectionSingle(detectorPolygon).calculateArea()
421 self.log.info(
"Using template input tract=%s, patch=%s, subfilter=%s" %
422 (dataId[
'tract'], dataId[
'patch'], dataId[
"subfilter"]))
423 coaddExposureRefList.append(coaddRef)
424 if dataId[
'tract']
in patchList:
425 patchList[dataId[
'tract']].append(dataId[
'patch'])
427 patchList[dataId[
'tract']] = [dataId[
'patch'], ]
428 dataIds.append(dataId)
430 if not overlappingArea:
431 raise pipeBase.NoWorkFound(
'No patches overlap detector')
433 self.checkPatchList(patchList)
435 coaddExposures = self.getDcrModel(patchList, inputs[
'dcrCoadds'], inputs[
'visitInfo'])
436 return pipeBase.Struct(coaddExposures=coaddExposures,
440 """Check that all of the DcrModel subfilters are present for each
446 Dict of the patches containing valid data for each tract.
451 If the number of exposures found
for a patch does
not match the
452 number of subfilters.
454 for tract
in patchList:
455 for patch
in set(patchList[tract]):
456 if patchList[tract].count(patch) != self.config.numSubfilters:
457 raise RuntimeError(
"Invalid number of DcrModel subfilters found: %d vs %d expected",
458 patchList[tract].count(patch), self.config.numSubfilters)
461 """Build DCR-matched coadds from a list of exposure references.
466 Dict of the patches containing valid data for each tract.
467 coaddRefs : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
469 DcrModels that overlap the detector.
471 Metadata
for the science image.
476 Coadd exposures that overlap the detector.
478 coaddExposureList = []
479 for tract
in patchList:
480 for patch
in set(patchList[tract]):
481 coaddRefList = [coaddRef
for coaddRef
in coaddRefs
484 dcrModel = DcrModel.fromQuantum(coaddRefList,
485 self.config.effectiveWavelength,
486 self.config.bandwidth,
487 self.config.numSubfilters)
488 coaddExposureList.append(dcrModel.buildMatchedExposure(visitInfo=visitInfo))
489 return coaddExposureList
493 condition = (coaddRef.dataId[
'tract'] == tract) & (coaddRef.dataId[
'patch'] == patch)
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.
A group of labels for a filter in an exposure or coadd.
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.
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
daf::base::PropertySet * set
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)
_selectDataRef(coaddRef, tract, patch)
checkPatchList(self, patchList)
getDcrModel(self, patchList, coaddRefs, visitInfo)