35from lsst.skymap
import BaseSkyMap
37from lsst.meas.algorithms
import CoaddPsf, CoaddPsfConfig, SubtractBackgroundTask
38from lsst.utils.timer
import timeMethod
44 "GetDcrTemplateConfig",
49 pipeBase.PipelineTaskConnections,
50 dimensions=(
"instrument",
"visit",
"detector"),
51 defaultTemplates={
"coaddName":
"goodSeeing",
"warpTypeSuffix":
"",
"fakesType":
""},
53 bbox = pipeBase.connectionTypes.Input(
54 doc=
"Bounding box of exposure to determine the geometry of the output template.",
55 name=
"{fakesType}calexp.bbox",
57 dimensions=(
"instrument",
"visit",
"detector"),
59 wcs = pipeBase.connectionTypes.Input(
60 doc=
"WCS of the exposure that we will construct the template for.",
61 name=
"{fakesType}calexp.wcs",
63 dimensions=(
"instrument",
"visit",
"detector"),
65 skyMap = pipeBase.connectionTypes.Input(
66 doc=
"Geometry of the tracts and patches that the coadds are defined on.",
67 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
68 dimensions=(
"skymap",),
69 storageClass=
"SkyMap",
71 coaddExposures = pipeBase.connectionTypes.Input(
72 doc=
"Coadds that may overlap the desired region, as possible inputs to the template."
73 " Will be restricted to those that directly overlap the projected bounding box.",
74 dimensions=(
"tract",
"patch",
"skymap",
"band"),
75 storageClass=
"ExposureF",
76 name=
"{fakesType}{coaddName}Coadd{warpTypeSuffix}",
79 deferGraphConstraint=
True,
82 template = pipeBase.connectionTypes.Output(
83 doc=
"Warped template, pixel matched to the bounding box and WCS.",
84 dimensions=(
"instrument",
"visit",
"detector"),
85 storageClass=
"ExposureF",
86 name=
"{fakesType}{coaddName}Diff_templateExp{warpTypeSuffix}",
90class GetTemplateConfig(
91 pipeBase.PipelineTaskConfig, pipelineConnections=GetTemplateConnections
93 templateBorderSize = pexConfig.Field(
96 doc=
"Number of pixels to grow the requested template image to account for warping",
98 warp = pexConfig.ConfigField(
99 dtype=afwMath.Warper.ConfigClass,
100 doc=
"warper configuration",
102 coaddPsf = pexConfig.ConfigField(
103 doc=
"Configuration for CoaddPsf",
104 dtype=CoaddPsfConfig,
106 varianceBackground = pexConfig.ConfigurableField(
107 target=SubtractBackgroundTask,
108 doc=
"Task to estimate the background variance.",
110 highVarianceThreshold = pexConfig.RangeField(
114 doc=
"Set the HIGH_VARIANCE mask plane for regions with variance"
115 " greater than the median by this factor.",
117 highVarianceMaskFraction = pexConfig.Field(
120 doc=
"Minimum fraction of unmasked pixels needed to set the"
121 " HIGH_VARIANCE mask plane.",
124 def setDefaults(self):
127 self.warp.cacheSize = 100000
128 self.coaddPsf.cacheSize = self.warp.cacheSize
131 self.warp.interpLength = 100
132 self.warp.warpingKernelName =
"lanczos3"
133 self.coaddPsf.warpingKernelName = self.warp.warpingKernelName
136 self.varianceBackground.algorithm =
"LINEAR"
137 self.varianceBackground.binSize = 32
138 self.varianceBackground.useApprox =
False
139 self.varianceBackground.statisticsProperty =
"MEDIAN"
140 self.varianceBackground.doFilterSuperPixels =
True
141 self.varianceBackground.ignoredPixelMask = [
"BAD",
149class GetTemplateTask(pipeBase.PipelineTask):
150 ConfigClass = GetTemplateConfig
151 _DefaultName =
"getTemplate"
153 def __init__(self, *args, **kwargs):
154 super().__init__(*args, **kwargs)
155 self.warper = afwMath.Warper.fromConfig(self.config.warp)
156 self.schema = afwTable.ExposureTable.makeMinimalSchema()
157 self.schema.addField(
158 "tract", type=np.int32, doc=
"Which tract this exposure came from."
160 self.schema.addField(
163 doc=
"Which patch in the tract this exposure came from.",
165 self.schema.addField(
168 doc=
"Weight for each exposure, used to make the CoaddPsf; should always be 1.",
170 self.makeSubtask(
"varianceBackground")
172 def runQuantum(self, butlerQC, inputRefs, outputRefs):
173 inputs = butlerQC.get(inputRefs)
174 bbox = inputs.pop(
"bbox")
175 wcs = inputs.pop(
"wcs")
176 coaddExposures = inputs.pop(
"coaddExposures")
177 skymap = inputs.pop(
"skyMap")
180 assert not inputs,
"runQuantum got more inputs than expected"
182 results = self.getExposures(coaddExposures, bbox, skymap, wcs)
183 physical_filter = butlerQC.quantum.dataId[
"physical_filter"]
185 coaddExposureHandles=results.coaddExposures,
188 dataIds=results.dataIds,
189 physical_filter=physical_filter,
190 visit=outputRefs.template.dataId[
"visit"],
192 butlerQC.put(outputs, outputRefs)
194 def getExposures(self, coaddExposureHandles, bbox, skymap, wcs):
195 """Return a data structure containing the coadds that overlap the
196 specified bbox projected onto the sky, and a corresponding data
197 structure of their dataIds.
198 These are the appropriate inputs to this task's `run` method.
200 The spatial index in the butler registry has generous padding and often
201 supplies patches near, but not directly overlapping the desired region.
202 This method filters the inputs so that `run` does not have to read in
203 all possibly-matching coadd exposures.
207 coaddExposureHandles : `iterable` \
208 [`lsst.daf.butler.DeferredDatasetHandle` of \
209 `lsst.afw.image.Exposure`]
210 Dataset handles to exposures that might overlap the desired
212 bbox : `lsst.geom.Box2I`
213 Template bounding box of the pixel geometry onto which the
214 coaddExposures will be resampled.
215 skymap : `lsst.skymap.SkyMap`
216 Geometry of the tracts and patches the coadds are defined on.
217 wcs : `lsst.afw.geom.SkyWcs`
218 Template WCS onto which the coadds will be resampled.
222 result : `lsst.pipe.base.Struct`
223 A struct with attributes:
226 Dict of coadd exposures that overlap the projected bbox,
228 (`dict` [`int`, `list` [`lsst.daf.butler.DeferredDatasetHandle` of
229 `lsst.afw.image.Exposure`] ]).
231 Dict of data IDs of the coadd exposures that overlap the
232 projected bbox, indexed on tract id
233 (`dict` [`int`, `list [`lsst.daf.butler.DataCoordinate`] ]).
238 Raised if no patches overlap the input detector bbox, or the input
242 raise pipeBase.NoWorkFound(
243 "WCS is None; cannot find overlapping exposures."
248 detectorCorners = wcs.pixelToSky(detectorPolygon.getCorners())
250 coaddExposures = collections.defaultdict(list)
251 dataIds = collections.defaultdict(list)
253 for coaddRef
in coaddExposureHandles:
254 dataId = coaddRef.dataId
255 patchWcs = skymap[dataId[
"tract"]].getWcs()
256 patchBBox = skymap[dataId[
"tract"]][dataId[
"patch"]].getOuterBBox()
262 detectorInPatchCoordinates = afwGeom.Polygon(patchWcs.skyToPixel(detectorCorners))
263 if patchPolygon.intersection(detectorInPatchCoordinates):
264 overlappingArea += patchPolygon.intersectionSingle(
265 detectorInPatchCoordinates
268 "Using template input tract=%s, patch=%s",
272 coaddExposures[dataId[
"tract"]].append(coaddRef)
273 dataIds[dataId[
"tract"]].append(dataId)
275 if not overlappingArea:
276 raise pipeBase.NoWorkFound(
"No patches overlap detector")
278 return pipeBase.Struct(coaddExposures=coaddExposures, dataIds=dataIds)
281 def run(self, *, coaddExposureHandles, bbox, wcs, dataIds, physical_filter, visit=None):
282 """Warp coadds from multiple tracts and patches to form a template to
283 subtract from a science image.
285 Tract and patch overlap regions are combined by a variance-weighted
286 average, and the variance planes are combined with the same weights,
287 not added in quadrature; the overlap regions are not statistically
288 independent, because they're derived from the same original data.
289 The PSF on the template is created by combining the CoaddPsf on each
290 template image into a meta-CoaddPsf.
294 coaddExposureHandles : `dict` [`int`, `list` of \
295 [`lsst.daf.butler.DeferredDatasetHandle` of \
296 `lsst.afw.image.Exposure`]]
297 Coadds to be mosaicked, indexed on tract id.
298 bbox : `lsst.geom.Box2I`
299 Template Bounding box of the detector geometry onto which to
300 resample the ``coaddExposureHandles``. Modified in-place to include the
302 wcs : `lsst.afw.geom.SkyWcs`
303 Template WCS onto which to resample the ``coaddExposureHandles``.
304 dataIds : `dict` [`int`, `list` [`lsst.daf.butler.DataCoordinate`]]
305 Record of the tract and patch of each coaddExposure, indexed on
307 physical_filter : `str`
308 Physical filter of the science image.
309 visit : `int`, optional
310 If supplied, over-write the visit ID in the template's visitInfo
311 so that downstream source injection tasks can link the template and
312 science image for the visit.
316 result : `lsst.pipe.base.Struct`
317 A struct with attributes:
320 A template coadd exposure assembled out of patches
321 (`lsst.afw.image.ExposureF`).
326 If no coadds are found with sufficient un-masked pixels.
328 band, photoCalib = self._checkInputs(dataIds, coaddExposureHandles)
330 bbox.grow(self.config.templateBorderSize)
334 for tract
in coaddExposureHandles:
335 maskedImages, catalog, totalBox = self._makeExposureCatalog(
336 coaddExposureHandles[tract], dataIds[tract]
338 warpedBox = computeWarpedBBox(catalog[0].wcs, bbox, wcs)
341 unwarped, count, included = self._merge(
342 maskedImages, warpedBox, catalog[0].wcs
348 "No valid pixels from coadd patches in tract %s; not including in output.",
352 warpedBox.clip(totalBox)
353 potentialInput = self.warper.warpExposure(
354 wcs, unwarped.subset(warpedBox), destBBox=bbox
360 potentialInput.mask.array
361 & potentialInput.mask.getPlaneBitMask(
"NO_DATA")
364 "No overlap from coadd patches in tract %s; not including in output.",
370 tempCatalog = afwTable.ExposureCatalog(self.schema)
371 tempCatalog.reserve(len(included))
373 tempCatalog.append(catalog[i])
374 catalogs.append(tempCatalog)
375 warped[tract] = potentialInput.maskedImage
378 raise pipeBase.NoWorkFound(
"No patches found to overlap science exposure.")
380 template, count, _ = self._merge(warped, bbox, wcs)
382 raise pipeBase.NoWorkFound(
"No valid pixels in warped template.")
385 catalog = afwTable.ExposureCatalog(self.schema)
386 catalog.reserve(sum([len(c)
for c
in catalogs]))
391 self.checkHighVariance(template)
392 if visit
is not None:
393 template.getInfo().setVisitInfo(
VisitInfo(id=visit))
395 template.setPhotoCalib(photoCalib)
396 template.setPsf(self._makePsf(template, catalog, wcs))
399 coaddInputs.ccds.extend(catalog, deep=
True)
400 template.getInfo().setCoaddInputs(coaddInputs)
401 return pipeBase.Struct(template=template)
404 """Set a mask plane for regions with unusually high variance.
408 template : `lsst.afw.image.Exposure`
409 The warped template exposure, which will be modified in place.
411 highVarianceMaskPlaneBit = template.mask.addMaskPlane(
"HIGH_VARIANCE")
412 ignoredPixelBits = template.mask.getPlaneBitMask(self.varianceBackground.config.ignoredPixelMask)
413 goodMask = (template.mask.array & ignoredPixelBits) == 0
414 goodFraction = np.count_nonzero(goodMask)/template.mask.array.size
415 if goodFraction < self.config.highVarianceMaskFraction:
416 self.log.info(
"Not setting HIGH_VARIANCE mask plane, only %2.1f%% of"
417 " pixels were unmasked for background estimation, but"
418 " %2.1f%% are required", 100*goodFraction, 100*self.config.highVarianceMaskFraction)
420 varianceExposure = template.clone()
421 varianceExposure.image.array = varianceExposure.variance.array
422 varianceBackground = self.varianceBackground.
run(varianceExposure).background.getImage().array
423 threshold = self.config.highVarianceThreshold*np.nanmedian(varianceBackground)
424 highVariancePix = varianceBackground > threshold
425 template.mask.array[highVariancePix] |= 2**highVarianceMaskPlaneBit
428 def _checkInputs(dataIds, coaddExposures):
429 """Check that the all the dataIds are from the same band and that
430 the exposures all have the same photometric calibration.
434 dataIds : `dict` [`int`, `list` [`lsst.daf.butler.DataCoordinate`]]
435 Record of the tract and patch of each coaddExposure.
436 coaddExposures : `dict` [`int`, `list` of \
437 [`lsst.daf.butler.DeferredDatasetHandle` of \
438 `lsst.afw.image.Exposure` or
439 `lsst.afw.image.Exposure`]]
440 Coadds to be mosaicked.
445 Filter band of all the input exposures.
446 photoCalib : `lsst.afw.image.PhotoCalib`
447 Photometric calibration of all of the input exposures.
452 Raised if the bands or calibrations of the input exposures are not
455 bands = set(dataId[
"band"]
for tract
in dataIds
for dataId
in dataIds[tract])
457 raise RuntimeError(f
"GetTemplateTask called with multiple bands: {bands}")
460 exposure.get(component=
"photoCalib")
461 for exposures
in coaddExposures.values()
462 for exposure
in exposures
464 if not all([photoCalibs[0] == x
for x
in photoCalibs]):
465 msg = f
"GetTemplateTask called with exposures with different photoCalibs: {photoCalibs}"
466 raise RuntimeError(msg)
467 photoCalib = photoCalibs[0]
468 return band, photoCalib
471 """Make an exposure catalog for one tract.
475 exposureRefs : `list` of [`lsst.daf.butler.DeferredDatasetHandle` of \
476 `lsst.afw.image.Exposure`]
477 Exposures to include in the catalog.
478 dataIds : `list` [`lsst.daf.butler.DataCoordinate`]
479 Data ids of each of the included exposures; must have "tract" and
484 images : `dict` [`lsst.afw.image.MaskedImage`]
485 MaskedImages of each of the input exposures, for warping.
486 catalog : `lsst.afw.table.ExposureCatalog`
487 Catalog of metadata for each exposure
488 totalBox : `lsst.geom.Box2I`
489 The union of the bounding boxes of all the input exposures.
491 catalog = afwTable.ExposureCatalog(self.schema)
492 catalog.reserve(len(exposureRefs))
493 exposures = (exposureRef.get()
for exposureRef
in exposureRefs)
497 for coadd, dataId
in zip(exposures, dataIds):
498 images[dataId] = coadd.maskedImage
499 bbox = coadd.getBBox()
500 totalBox = totalBox.expandedTo(bbox)
501 record = catalog.addNew()
502 record.setPsf(coadd.psf)
503 record.setWcs(coadd.wcs)
504 record.setPhotoCalib(coadd.photoCalib)
506 record.setValidPolygon(afwGeom.Polygon(
geom.Box2D(bbox).getCorners()))
507 record.set(
"tract", dataId[
"tract"])
508 record.set(
"patch", dataId[
"patch"])
511 record.set(
"weight", 1)
513 return images, catalog, totalBox
515 def _merge(self, maskedImages, bbox, wcs):
516 """Merge the images that came from one tract into one larger image,
517 ignoring NaN pixels and non-finite variance pixels from individual
522 maskedImages : `dict` [`lsst.afw.image.MaskedImage` or
523 `lsst.afw.image.Exposure`]
524 Images to be merged into one larger bounding box.
525 bbox : `lsst.geom.Box2I`
526 Bounding box defining the image to merge into.
527 wcs : `lsst.afw.geom.SkyWcs`
528 WCS of all of the input images to set on the output image.
532 merged : `lsst.afw.image.MaskedImage`
533 Merged image with all of the inputs at their respective bbox
536 Count of the number of good pixels (those with positive weights)
538 included : `list` [`int`]
539 List of indexes of patches that were included in the merged
540 result, to be used to trim the exposure catalog.
542 merged = afwImage.ExposureF(bbox, wcs)
543 weights = afwImage.ImageF(bbox)
545 for i, (dataId, maskedImage)
in enumerate(maskedImages.items()):
547 clippedBox =
geom.Box2I(maskedImage.getBBox())
548 clippedBox.clip(bbox)
549 if clippedBox.area == 0:
550 self.log.debug(
"%s does not overlap template region.", dataId)
552 maskedImage = maskedImage.subset(clippedBox)
554 good = (maskedImage.variance.array > 0) & (
555 np.isfinite(maskedImage.variance.array)
557 weight = maskedImage.variance.array[good] ** (-0.5)
558 bad = np.isnan(maskedImage.image.array) | ~good
561 maskedImage.image.array[bad] = 0.0
562 maskedImage.variance.array[bad] = 0.0
564 maskedImage.mask.array[bad] = 0
568 maskedImage.image.array[good] *= weight
569 maskedImage.variance.array[good] *= weight
570 weights[clippedBox].array[good] += weight
573 merged.maskedImage[clippedBox] += maskedImage
576 good = weights.array > 0
581 weights = weights.array[good]
582 merged.image.array[good] /= weights
583 merged.variance.array[good] /= weights
585 merged.mask.array[~good] |= merged.mask.getPlaneBitMask(
"NO_DATA")
587 return merged, good.sum(), included
590 """Return a PSF containing the PSF at each of the input regions.
592 Note that although this includes all the exposures from the catalog,
593 the PSF knows which part of the template the inputs came from, so when
594 evaluated at a given position it will not include inputs that never
595 went in to those pixels.
599 template : `lsst.afw.image.Exposure`
600 Generated template the PSF is for.
601 catalog : `lsst.afw.table.ExposureCatalog`
602 Catalog of exposures that went into the template that contains all
604 wcs : `lsst.afw.geom.SkyWcs`
605 WCS of the template, to warp the PSFs to.
609 coaddPsf : `lsst.meas.algorithms.CoaddPsf`
610 The meta-psf constructed from all of the input catalogs.
614 boolmask = template.mask.array & template.mask.getPlaneBitMask(
"NO_DATA") == 0
616 centerCoord = afwGeom.SpanSet.fromMask(maskx, 1).computeCentroid()
618 ctrl = self.config.coaddPsf.makeControl()
620 catalog, wcs, centerCoord, ctrl.warpingKernelName, ctrl.cacheSize
626 GetTemplateConnections,
627 dimensions=(
"instrument",
"visit",
"detector"),
628 defaultTemplates={
"coaddName":
"dcr",
"warpTypeSuffix":
"",
"fakesType":
""},
630 visitInfo = pipeBase.connectionTypes.Input(
631 doc=
"VisitInfo of calexp used to determine observing conditions.",
632 name=
"{fakesType}calexp.visitInfo",
633 storageClass=
"VisitInfo",
634 dimensions=(
"instrument",
"visit",
"detector"),
636 dcrCoadds = pipeBase.connectionTypes.Input(
637 doc=
"Input DCR template to match and subtract from the exposure",
638 name=
"{fakesType}dcrCoadd{warpTypeSuffix}",
639 storageClass=
"ExposureF",
640 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
645 def __init__(self, *, config=None):
646 super().__init__(config=config)
647 self.inputs.remove(
"coaddExposures")
650class GetDcrTemplateConfig(
651 GetTemplateConfig, pipelineConnections=GetDcrTemplateConnections
653 numSubfilters = pexConfig.Field(
654 doc=
"Number of subfilters in the DcrCoadd.",
658 effectiveWavelength = pexConfig.Field(
659 doc=
"Effective wavelength of the filter in nm.",
663 bandwidth = pexConfig.Field(
664 doc=
"Bandwidth of the physical filter.",
670 if self.effectiveWavelength
is None or self.bandwidth
is None:
672 "The effective wavelength and bandwidth of the physical filter "
673 "must be set in the getTemplate config for DCR coadds. "
674 "Required until transmission curves are used in DM-13668."
678class GetDcrTemplateTask(GetTemplateTask):
679 ConfigClass = GetDcrTemplateConfig
680 _DefaultName =
"getDcrTemplate"
682 def runQuantum(self, butlerQC, inputRefs, outputRefs):
683 inputs = butlerQC.get(inputRefs)
684 bbox = inputs.pop(
"bbox")
685 wcs = inputs.pop(
"wcs")
686 dcrCoaddExposureHandles = inputs.pop(
"dcrCoadds")
687 skymap = inputs.pop(
"skyMap")
688 visitInfo = inputs.pop(
"visitInfo")
691 assert not inputs,
"runQuantum got more inputs than expected"
693 results = self.getExposures(
694 dcrCoaddExposureHandles, bbox, skymap, wcs, visitInfo
696 physical_filter = butlerQC.quantum.dataId[
"physical_filter"]
698 coaddExposureHandles=results.coaddExposures,
701 dataIds=results.dataIds,
702 physical_filter=physical_filter,
704 butlerQC.put(outputs, outputRefs)
706 def getExposures(self, dcrCoaddExposureHandles, bbox, skymap, wcs, visitInfo):
707 """Return lists of coadds and their corresponding dataIds that overlap
710 The spatial index in the registry has generous padding and often
711 supplies patches near, but not directly overlapping the detector.
712 Filters inputs so that we don't have to read in all input coadds.
716 dcrCoaddExposureHandles : `list` \
717 [`lsst.daf.butler.DeferredDatasetHandle` of \
718 `lsst.afw.image.Exposure`]
719 Data references to exposures that might overlap the detector.
720 bbox : `lsst.geom.Box2I`
721 Template Bounding box of the detector geometry onto which to
722 resample the coaddExposures.
723 skymap : `lsst.skymap.SkyMap`
724 Input definition of geometry/bbox and projection/wcs for
726 wcs : `lsst.afw.geom.SkyWcs`
727 Template WCS onto which to resample the coaddExposures.
728 visitInfo : `lsst.afw.image.VisitInfo`
729 Metadata for the science image.
733 result : `lsst.pipe.base.Struct`
734 A struct with attibutes:
737 Dict of coadd exposures that overlap the projected bbox,
739 (`dict` [`int`, `list` [`lsst.afw.image.Exposure`] ]).
741 Dict of data IDs of the coadd exposures that overlap the
742 projected bbox, indexed on tract id
743 (`dict` [`int`, `list [`lsst.daf.butler.DataCoordinate`] ]).
748 Raised if no patches overlatp the input detector bbox.
753 raise pipeBase.NoWorkFound(
"Exposure has no WCS; cannot create a template.")
757 dataIds = collections.defaultdict(list)
759 for coaddRef
in dcrCoaddExposureHandles:
760 dataId = coaddRef.dataId
761 subfilter = dataId[
"subfilter"]
762 patchWcs = skymap[dataId[
"tract"]].getWcs()
763 patchBBox = skymap[dataId[
"tract"]][dataId[
"patch"]].getOuterBBox()
764 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
765 patchPolygon = afwGeom.Polygon(wcs.skyToPixel(patchCorners))
766 if patchPolygon.intersection(detectorPolygon):
767 overlappingArea += patchPolygon.intersectionSingle(
771 "Using template input tract=%s, patch=%s, subfilter=%s"
772 % (dataId[
"tract"], dataId[
"patch"], dataId[
"subfilter"])
774 if dataId[
"tract"]
in patchList:
775 patchList[dataId[
"tract"]].append(dataId[
"patch"])
777 patchList[dataId[
"tract"]] = [
781 dataIds[dataId[
"tract"]].append(dataId)
783 if not overlappingArea:
784 raise pipeBase.NoWorkFound(
"No patches overlap detector")
786 self.checkPatchList(patchList)
788 coaddExposures = self.getDcrModel(patchList, dcrCoaddExposureHandles, visitInfo)
789 return pipeBase.Struct(coaddExposures=coaddExposures, dataIds=dataIds)
792 """Check that all of the DcrModel subfilters are present for each
798 Dict of the patches containing valid data for each tract.
803 If the number of exposures found for a patch does not match the
804 number of subfilters.
806 for tract
in patchList:
807 for patch
in set(patchList[tract]):
808 if patchList[tract].count(patch) != self.config.numSubfilters:
810 "Invalid number of DcrModel subfilters found: %d vs %d expected",
811 patchList[tract].count(patch),
812 self.config.numSubfilters,
816 """Build DCR-matched coadds from a list of exposure references.
821 Dict of the patches containing valid data for each tract.
822 coaddRefs : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
823 Data references to `~lsst.afw.image.Exposure` representing
824 DcrModels that overlap the detector.
825 visitInfo : `lsst.afw.image.VisitInfo`
826 Metadata for the science image.
830 coaddExposures : `list` [`lsst.afw.image.Exposure`]
831 Coadd exposures that overlap the detector.
833 coaddExposures = collections.defaultdict(list)
834 for tract
in patchList:
835 for patch
in set(patchList[tract]):
838 for coaddRef
in coaddRefs
842 dcrModel = DcrModel.fromQuantum(
844 self.config.effectiveWavelength,
845 self.config.bandwidth,
846 self.config.numSubfilters,
848 coaddExposures[tract].append(dcrModel.buildMatchedExposureHandle(visitInfo=visitInfo))
849 return coaddExposures
853 condition = (coaddRef.dataId[
"tract"] == tract) & (
854 coaddRef.dataId[
"patch"] == patch
run(self, *, coaddExposureHandles, bbox, wcs, dataIds, physical_filter, visit=None)
checkHighVariance(self, template)
_makePsf(self, template, catalog, wcs)
_makeExposureCatalog(self, exposureRefs, dataIds)
getDcrModel(self, patchList, coaddRefs, visitInfo)
_merge(self, maskedImages, bbox, wcs)
checkPatchList(self, patchList)
_selectDataRef(coaddRef, tract, patch)