26from astropy.stats
import gaussian_sigma_to_fwhm
42from lsst.images
import (
45 DifferenceImageTemplateInfo,
46 DifferenceImageTemplateInput,
51 get_legacy_optional_mask_planes,
52 get_legacy_template_mask_planes,
54from lsst.skymap
import BaseSkyMap
57from lsst.meas.algorithms
import CoaddPsf, CoaddPsfConfig, SubtractBackgroundTask, ScaleVarianceTask
58from lsst.utils.timer
import timeMethod
64 "GetDcrTemplateConfig",
68"""Width, in N sigma, of the Gaussian approximation to the CoaddPsf."""
70_UNKNOWN_INSTRUMENT =
"UNKNOWN"
71"""Instrument recorded for a coadd or template made from observations of an
75_FOOTPRINT_MARGIN = 32.0
76"""Distance, in coadd pixels, by which the science image's footprint in a
77coadd is grown before a coadd input is clipped to it. It must exceed twice
78the distance by which a distorted science image edge departs from a straight
79line between its corners, and limits how far outside the science image its
85 """Return a chainable transformation between two pixel frames.
89 fromWcs, toWcs : `lsst.afw.geom.SkyWcs`
90 WCSs of the input and output pixels.
94 transform : `lsst.images.Transform`
95 Transform from ``fromWcs`` pixels to ``toWcs`` pixels.
98 GeneralFrame(unit=astropy.units.pix),
99 GeneralFrame(unit=astropy.units.pix)
103def _getImageOverlap(record, coaddWcs, scienceFootprint, coaddToScienceTransform, scienceBox, border):
104 """Return the region enclosing the overlap of the science image and one
105 input image of the coadd.
107 Note that there are three different WCS's involved here: the WCS of the
108 original image that was warped to contribute to the coadd, the coadd WCS,
109 and the current science image WCS that we are building a template for.
113 record : `lsst.afw.table.ExposureRecord`
114 Input record of a coadd, with the input's WCS, bounding box and
115 optional valid polygon.
116 coaddWcs : `lsst.afw.geom.SkyWcs`
118 scienceFootprint : `lsst.images.Region`
119 ``scienceBox`` in coadd pixels, grown by ``_FOOTPRINT_MARGIN``.
120 coaddToScienceTransform : `lsst.images.Transform`
121 Transform from coadd pixels to science image pixels.
122 scienceBox : `lsst.images.Box`
123 Bounding box of the science image.
125 Width, in input pixels, of the border of the input that warping left
130 footprint : `lsst.images.Polygon` or `None`
131 The part of ``scienceBox`` the input's warp covers, in science image
132 pixels, or `None` if it covers none of it.
135 inputBox.grow(-border)
136 inputBounds = Polygon.from_legacy(afwGeom.Polygon(inputBox))
137 polygon = record.getValidPolygon()
140 if polygon
is not None:
141 inputBounds = inputBounds.intersection(Polygon.from_legacy(polygon))
145 inputBounds.transform(inputToCoadd)
146 .intersection(scienceFootprint)
147 .transform(coaddToScienceTransform)
150 footprint = footprint.convex_hull().intersection(scienceBox)
151 except NoOverlapError:
153 return Polygon.from_box(footprint)
if isinstance(footprint, Box)
else footprint
157 """Return the width of the border of an image that warping leaves out.
159 A warped pixel is computed only where the whole kernel lies on the image.
164 Name of the warping kernel, as `lsst.afw.math.Warper` takes it.
169 Width of the border on each side, in pixels from the edge of the
170 image's bounding box; 2.5 for ``lanczos3``.
173 return kernel.getCtr().getX() + 0.5
178 """Compute the phase-averaged sum of squared values of a warping kernel.
180 Warping with a kernel normalized to unit sum multiplies the variance by
181 this sum, which is less than one. The power the warp took off the
182 diagonal became covariance between neighboring pixels, which a variance
183 plane cannot represent, so dividing by it restores the noise power: the
184 variance of a flux measured over a region and not a single pixel.
189 Name of the warping kernel, as `lsst.afw.math.Warper` takes it.
190 nPhase : `int`, optional
191 Number of sub-pixel phases to average over.
196 Sum of the squared 2-D kernel values, averaged over sub-pixel
197 phase. Converges with large ``nPhase`` to 0.798 for ``lanczos3``,
198 4/9 for ``bilinear``, and 1 for no interpolation.
202 The kernels are separable and the phases of the two axes independent,
203 so this is the square of the 1-D average. The warper normalizes the
204 kernel to unit sum at each phase, which the analytic Lanczos integral
205 does not include, so the average is computed from the kernel itself.
209 kernel = afwMath.Warper(kernelName, cacheSize=0).getWarpingKernel()
210 image = afwImage.ImageD(kernel.getDimensions())
212 for phase
in (np.arange(nPhase) + 0.5)/nPhase:
213 kernel.setKernelParameters((float(phase), 0.0))
214 kernel.computeImage(image, doNormalize=
True)
215 total += float((image.array.sum(axis=0)**2).sum())
216 return (total/nPhase)**2
220 pipeBase.PipelineTaskConnections,
221 dimensions=(
"instrument",
"visit",
"detector"),
222 defaultTemplates={
"coaddName":
"goodSeeing",
"warpTypeSuffix":
"",
"fakesType":
""},
224 bbox = pipeBase.connectionTypes.Input(
225 doc=
"Bounding box of exposure to determine the geometry of the output template.",
226 name=
"{fakesType}calexp.bbox",
227 storageClass=
"Box2I",
228 dimensions=(
"instrument",
"visit",
"detector"),
229 deprecated=
"Replaced by the `detector` connection. Will be removed after v31.",
231 detector = pipeBase.connectionTypes.Input(
232 doc=
"Detector of the exposure that we will construct the template for."
233 " Its bounding box sets the geometry of the output template.",
234 name=
"{fakesType}calexp.detector",
235 storageClass=
"Detector",
236 dimensions=(
"instrument",
"visit",
"detector"),
238 obs_info = pipeBase.connectionTypes.Input(
239 doc=
"Observation metadata of the exposure the template is built for."
240 " Only read with output_image_type='future', which needs this to set"
241 " the obs_info component of a lsst.images.VisitImage.",
242 name=
"{fakesType}calexp.obs_info",
243 storageClass=
"ObservationInfo",
244 dimensions=(
"instrument",
"visit",
"detector"),
246 wcs = pipeBase.connectionTypes.Input(
247 doc=
"WCS of the exposure that we will construct the template for.",
248 name=
"{fakesType}calexp.wcs",
250 dimensions=(
"instrument",
"visit",
"detector"),
252 skyMap = pipeBase.connectionTypes.Input(
253 doc=
"Geometry of the tracts and patches that the coadds are defined on.",
254 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
255 dimensions=(
"skymap",),
256 storageClass=
"SkyMap",
258 coaddExposures = pipeBase.connectionTypes.Input(
259 doc=
"Coadds that may overlap the desired region, as possible inputs to the template."
260 " Will be restricted to those that directly overlap the projected bounding box.",
261 dimensions=(
"tract",
"patch",
"skymap",
"band"),
262 storageClass=
"ExposureF",
263 name=
"{fakesType}{coaddName}Coadd{warpTypeSuffix}",
266 deferGraphConstraint=
True,
269 template = pipeBase.connectionTypes.Output(
270 doc=
"Warped template, pixel matched to the bounding box and WCS.",
271 dimensions=(
"instrument",
"visit",
"detector"),
272 storageClass=
"ExposureF",
273 name=
"{fakesType}{coaddName}Diff_templateExp{warpTypeSuffix}",
276 def __init__(self, *, config=None):
277 super().__init__(config=config)
280 if config.requireCoaddAtGraphBuild:
281 self.coaddExposures = dataclasses.replace(
283 deferGraphConstraint=
False,
285 if config.output_image_type ==
"future":
286 self.template = dataclasses.replace(self.template, storageClass=
"DifferenceImage")
291class GetTemplateConfig(
292 pipeBase.PipelineTaskConfig, pipelineConnections=GetTemplateConnections
294 templateBorderSize = pexConfig.Field(
297 doc=
"Number of pixels to grow the requested template image to account for warping",
299 warp = pexConfig.ConfigField(
300 dtype=afwMath.Warper.ConfigClass,
301 doc=
"warper configuration",
303 doCorrectVarianceWarpCorrelation = pexConfig.Field(
306 doc=
"Correct the template variance for the correlation that warping"
307 " introduced between neighboring pixels? This covers both warps: the"
308 " one that built the coadds, named by coaddWarpKernel, and the one"
309 " this task performs, named by warp.warpingKernelName. It is a single"
310 " scalar factor, fixed entirely by those two kernels and independent"
311 " of the pixel scales involved.",
313 coaddWarpKernel = pexConfig.ChoiceField(
317 "bilinear":
"bilinear interpolation",
318 "lanczos3":
"Lanczos kernel of order 3",
319 "lanczos4":
"Lanczos kernel of order 4",
320 "lanczos5":
"Lanczos kernel of order 5",
322 doc=
"Warping kernel that was used to build the input coadds. This"
323 " cannot be recovered from the coadds themselves. It is used to"
324 " correct the variance if doCorrectVarianceWarpCorrelation is True,"
325 " and to trim the coadd inputs bboxes to account for the size of the"
328 coaddPsf = pexConfig.ConfigField(
329 doc=
"Configuration for CoaddPsf",
330 dtype=CoaddPsfConfig,
332 fwhmExposureGrid = pexConfig.Field(
333 doc=
"Grid size to compute the average PSF FWHM in an exposure (pixels)",
337 fwhmExposureBuffer = pexConfig.Field(
338 doc=
"Fractional buffer margin to be left out of all sides of the image during construction"
339 " of grid to compute average PSF FWHM in an exposure",
343 varianceBackground = pexConfig.ConfigurableField(
344 target=SubtractBackgroundTask,
345 doc=
"Task to estimate the background variance.",
347 highVarianceThreshold = pexConfig.RangeField(
351 doc=
"Set the HIGH_VARIANCE mask plane for regions with variance"
352 " greater than the median by this factor.",
354 highVarianceMaskFraction = pexConfig.Field(
357 doc=
"Minimum fraction of unmasked pixels needed to set the"
358 " HIGH_VARIANCE mask plane.",
360 doScaleVariance = pexConfig.Field(
363 doc=
"Scale the variance of the template image to match its per-pixel"
364 " scatter? Warping correlates the noise, so when combined with"
365 " doCorrectVarianceWarpCorrelation this overcounts the correction.",
367 scaleVariance = pexConfig.ConfigurableField(
368 target=ScaleVarianceTask,
369 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
371 requireCoaddAtGraphBuild = pexConfig.Field(
374 doc=
"If True, include the coadd dataset existence in the"
375 " initial butler query during QuantumGraph generation.",
377 output_image_type = pexConfig.ChoiceField[str](
378 "Which image type to use for the output template.",
380 "legacy":
"Write as a lsst.afw.image.ExposureF.",
381 "future":
"Write as a lsst.images.DifferenceImage.",
386 raiseOnUndefinedMaskMap = pexConfig.Field(
389 doc=
"Raise if the template has pixels set in a mask plane that"
390 " `lsst.images` does not map? If False, log a warning and drop that"
391 " plane from the output instead."
392 " Only used with output_image_type='future'.",
398 self.warp.cacheSize = 100000
399 self.coaddPsf.cacheSize = self.warp.cacheSize
402 self.warp.interpLength = 100
403 self.warp.warpingKernelName =
"lanczos3"
404 self.coaddPsf.warpingKernelName = self.warp.warpingKernelName
407 self.varianceBackground.algorithm =
"LINEAR"
408 self.varianceBackground.binSize = 32
409 self.varianceBackground.useApprox =
False
410 self.varianceBackground.statisticsProperty =
"MEDIAN"
411 self.varianceBackground.doFilterSuperPixels =
True
412 self.varianceBackground.ignoredPixelMask = [
"BAD",
421 ConfigClass = GetTemplateConfig
422 _DefaultName =
"getTemplate"
426 if self.config.doScaleVariance:
427 self.makeSubtask(
"scaleVariance")
428 self.
warper = afwMath.Warper.fromConfig(self.config.warp)
429 self.
schema = afwTable.ExposureTable.makeMinimalSchema()
431 "tract", type=np.int32, doc=
"Which tract this exposure came from."
436 doc=
"Which patch in the tract this exposure came from.",
441 doc=
"Weight for each exposure, used to make the CoaddPsf; should always be 1.",
447 doc=
"Instrument whose observations were coadded to make the template, or UNKNOWN.",
449 self.makeSubtask(
"varianceBackground")
452 inputs = butlerQC.get(inputRefs)
453 detector = inputs.pop(
"detector")
454 bbox = detector.getBBox()
455 wcs = inputs.pop(
"wcs")
456 obsInfo = inputs.pop(
"obs_info",
None)
457 coaddExposures = inputs.pop(
"coaddExposures")
458 skymap = inputs.pop(
"skyMap")
461 assert not inputs,
"runQuantum got more inputs than expected"
463 results = self.
getExposures(coaddExposures, bbox, skymap, wcs)
464 physical_filter = butlerQC.quantum.dataId[
"physical_filter"]
466 coaddExposureHandles=results.coaddExposures,
469 dataIds=results.dataIds,
470 physical_filter=physical_filter,
471 visit=outputRefs.template.dataId[
"visit"],
472 instrument=butlerQC.quantum.dataId[
"instrument"],
474 if self.config.output_image_type ==
"future":
475 dataId = butlerQC.quantum.dataId
476 exposureRecord = record_from_obs_info(obsInfo, dataId[
"instrument"], dataId[
"visit"],
479 inputRefs.coaddExposures,
481 exposureRecord=exposureRecord,
482 templateInputs=outputs.templateInputs)
483 butlerQC.put(outputs, outputRefs)
486 templateInputs=None):
487 """Convert a template image to DifferenceImage format.
489 This replaces ``template`` with an `lsst.images.DifferenceImage`
490 instance and records the coadds contributed to it, along with the
491 instrument they were made from and their input image identifiers.
492 The Gaussian PSF set by `run` is converted to an
493 `lsst.images.psfs.GaussianPointSpreadFunction`.
497 template : `lsst.afw.image.Exposure`
498 Template to convert. Its detector is replaced with ``detector``
499 and its mask is modified in place.
500 coadd_refs : `list` [`lsst.daf.butler.DatasetRef`]
501 References to the coadds that may have gone into the template.
502 May be a superset of the coadds that were used; supplies the
503 dataset id and RUN collection recorded for each coadd that was.
504 detector : `lsst.afw.cameraGeom.Detector`
505 Detector of the science image that the template was built for.
506 exposureRecord : `lsst.daf.butler.DimensionRecord`
507 The ``exposure`` record of the observation the template was built
508 for, which supplies the observation metadata recorded on the
510 templateInputs : `dict` [`tuple` [`int`, `int`], \
511 `list` [`lsst.images.DifferenceImageTemplateInput`]], optional
512 Input image records of each coadd, keyed by tract and patch, as
513 returned by `run`. Coadds not listed record no inputs.
517 convertedTemplate : `lsst.images.DifferenceImage`
523 Raised if the template has pixels set in a mask plane that
524 `lsst.images.get_legacy_template_mask_planes` does not map and
525 ``config.raiseOnUndefinedMaskMap`` is True, or if ``coadd_refs``
526 spans more than one skymap.
530 catalog = template.getInfo().getCoaddInputs().ccds
531 coaddPsf = self.
_makePsf(template, catalog, template.getWcs())
533 instrument = catalog[0][
"instrument"]
534 template.setDetector(detector)
535 skymaps = {ref.dataId[
"skymap"]
for ref
in coadd_refs}
536 if len(skymaps) != 1:
537 raise RuntimeError(
"Expected the template inputs to come from one skymap;"
538 f
" got {sorted(skymaps)}.")
539 butler_info = {(ref.dataId[
"tract"], ref.dataId[
"patch"]): (ref.id, ref.run)
540 for ref
in coadd_refs}
541 planeMap = get_legacy_template_mask_planes()
542 if not self.config.raiseOnUndefinedMaskMap:
544 convertedTemplate = DifferenceImage.from_legacy(
546 exposure_record=exposureRecord,
547 unit=astropy.units.nJy,
551 convertedTemplate.templates = DifferenceImageTemplateInfo.from_legacy_psf(
552 convertedTemplate.sky_projection.pixel_frame,
557 instrument=instrument,
560 for info
in convertedTemplate.templates:
561 info.inputs = templateInputs.get((info.tract, info.patch), [])
562 return convertedTemplate
565 """Clear the pixels of every mask plane that conversion would reject.
569 mask : `lsst.afw.image.Mask`
570 Template mask to modify in place.
571 planeMap : `dict` [`str`, `lsst.images.MaskPlane`]
572 Plane map the template will be converted with. The optional
573 planes of `lsst.images.get_legacy_optional_mask_planes` are kept
574 as well, since conversion adds them when they have pixels set.
576 keep = planeMap.keys() | get_legacy_optional_mask_planes().keys()
577 for name
in mask.getMaskPlaneDict():
580 bitmask = mask.getPlaneBitMask(name)
581 nSet = np.count_nonzero(mask.array & bitmask)
583 self.log.warning(
"Dropping mask plane %s from the template: %d pixels are set, but"
584 " the template plane map does not include it.", name, nSet)
585 mask.array &= ~bitmask
588 """Return a data structure containing the coadds that overlap the
589 specified bbox projected onto the sky, and a corresponding data
590 structure of their dataIds.
591 These are the appropriate inputs to this task's `run` method.
593 The spatial index in the butler registry has generous padding and often
594 supplies patches near, but not directly overlapping the desired region.
595 This method filters the inputs so that `run` does not have to read in
596 all possibly-matching coadd exposures.
600 coaddExposureHandles : `iterable` \
601 [`lsst.daf.butler.DeferredDatasetHandle` of \
602 `lsst.afw.image.Exposure`]
603 Dataset handles to exposures that might overlap the desired
605 bbox : `lsst.geom.Box2I`
606 Template bounding box of the pixel geometry onto which the
607 coaddExposures will be resampled.
608 skymap : `lsst.skymap.SkyMap`
609 Geometry of the tracts and patches the coadds are defined on.
610 wcs : `lsst.afw.geom.SkyWcs`
611 Template WCS onto which the coadds will be resampled.
615 result : `lsst.pipe.base.Struct`
616 A struct with attributes:
619 Dict of coadd exposures that overlap the projected bbox,
621 (`dict` [`int`, `list` [`lsst.daf.butler.DeferredDatasetHandle` of
622 `lsst.afw.image.Exposure`] ]).
624 Dict of data IDs of the coadd exposures that overlap the
625 projected bbox, indexed on tract id
626 (`dict` [`int`, `list [`lsst.daf.butler.DataCoordinate`] ]).
631 Raised if no patches overlap the input detector bbox, or the input
635 raise pipeBase.NoWorkFound(
636 "WCS is None; cannot find overlapping exposures."
641 detectorCorners = wcs.pixelToSky(detectorPolygon.getCorners())
643 coaddExposures = collections.defaultdict(list)
644 dataIds = collections.defaultdict(list)
646 for coaddRef
in coaddExposureHandles:
647 dataId = coaddRef.dataId
648 patchWcs = skymap[dataId[
"tract"]].getWcs()
649 patchBBox = skymap[dataId[
"tract"]][dataId[
"patch"]].getOuterBBox()
650 patchPolygon = afwGeom.Polygon(
geom.Box2D(patchBBox))
655 detectorInPatchCoordinates = afwGeom.Polygon(patchWcs.skyToPixel(detectorCorners))
656 if patchPolygon.intersection(detectorInPatchCoordinates):
657 overlappingArea += patchPolygon.intersectionSingle(
658 detectorInPatchCoordinates
661 "Using template input tract=%s, patch=%s",
665 coaddExposures[dataId[
"tract"]].append(coaddRef)
666 dataIds[dataId[
"tract"]].append(dataId)
668 if not overlappingArea:
669 raise pipeBase.NoWorkFound(
"No patches overlap detector")
671 return pipeBase.Struct(coaddExposures=coaddExposures, dataIds=dataIds)
674 def run(self, *, coaddExposureHandles, bbox, wcs, dataIds, physical_filter, visit=None, instrument=None):
675 """Warp coadds from multiple tracts and patches to form a template to
676 subtract from a science image.
678 Tract and patch overlap regions are combined by a variance-weighted
679 average, and the variance planes are combined with the same weights,
680 not added in quadrature; the overlap regions are not statistically
681 independent, because they're derived from the same original data.
682 The PSF of the template is a Gaussian with the same width as the
683 CoaddPsf evaluated at the center of the detector.
685 The template is built from coadds of one instrument: the science
686 image's ``instrument`` if any coadd was made from it, and otherwise the
687 one that most of the coadds were made from. Coadds made from another
688 instrument are dropped, and so are coadds that record no instrument
689 unless none of the coadds records one.
693 coaddExposureHandles : `dict` [`int`, `list` of \
694 [`lsst.daf.butler.DeferredDatasetHandle` of \
695 `lsst.afw.image.Exposure`]]
696 Coadds to be mosaicked, indexed on tract id.
697 bbox : `lsst.geom.Box2I`
698 Input Bounding box of the detector geometry onto which to
699 resample the ``coaddExposureHandles``. The output template bbox
700 will be grown by ``templateBorderSize``.
701 wcs : `lsst.afw.geom.SkyWcs`
702 Template WCS onto which to resample the ``coaddExposureHandles``.
703 dataIds : `dict` [`int`, `list` [`lsst.daf.butler.DataCoordinate`]]
704 Record of the tract and patch of each coaddExposure, indexed on
706 physical_filter : `str`
707 Physical filter of the science image.
708 visit : `int`, optional
709 If supplied, over-write the visit ID in the template's visitInfo
710 so that downstream source injection tasks can link the template and
711 science image for the visit.
712 instrument : `str`, optional
713 Instrument of the science image. Coadds made from it are used in
714 preference to coadds from any other instrument.
718 result : `lsst.pipe.base.Struct`
719 A struct with attributes:
722 A template coadd exposure assembled out of patches, with
723 pixels in nJy. Its PSF is a Gaussian approximation to the
724 CoaddPsf of its inputs (`lsst.afw.image.ExposureF`).
726 Input image records of each coadd that cover part of the
727 science image, keyed by tract and patch (`dict`
728 [`tuple` [`int`, `int`],
729 `list` [`lsst.images.DifferenceImageTemplateInput`]]). Empty
730 unless ``config.output_image_type`` is ``"future"``.
735 If no coadds are found with sufficient un-masked pixels.
737 If the template is not known to be from the science image's
738 instrument and no coadd records the plate scale of its input
739 images, so the template variance cannot be corrected.
741 band, photoCalib, templateInstrument = self.
_checkInputs(dataIds, coaddExposureHandles, instrument)
745 bbox.grow(self.config.templateBorderSize)
750 plateScaleFactor = 1.0
if instrument == templateInstrument
else None
752 for tract
in coaddExposureHandles:
754 coaddExposureHandles[tract], dataIds[tract], templateInstrument
756 if len(catalog) == 0:
763 if plateScaleFactor
is None:
765 warpedBox = computeWarpedBBox(catalog[0].wcs, bbox, wcs)
768 unwarped, count, included = self.
_merge(
769 maskedImages, warpedBox, catalog[0].wcs
775 "No valid pixels from coadd patches in tract %s; not including in output.",
779 warpedBox.clip(totalBox)
780 potentialInput = self.
warper.warpExposure(
781 wcs, unwarped.subset(warpedBox), destBBox=bbox
786 potentialInput.mask.array
787 & potentialInput.mask.getPlaneBitMask(
"NO_DATA")
790 "No overlap from coadd patches in tract %s; not including in output.",
796 tempCatalog = afwTable.ExposureCatalog(self.
schema)
797 tempCatalog.reserve(len(included))
799 tempCatalog.append(catalog[i])
800 inputs.append(coaddInputs[i])
801 catalogs.append(tempCatalog)
802 warped[tract] = potentialInput.maskedImage
805 raise pipeBase.NoWorkFound(
"No patches found to overlap science exposure.")
808 template, count, _ = self.
_merge(warped, bbox, wcs)
810 raise pipeBase.NoWorkFound(
"No valid pixels in warped template.")
812 if self.config.doScaleVariance:
816 varianceFactor = self.scaleVariance.
run(template.maskedImage)
817 self.log.info(
"Template variance scaling factor: %.2f", varianceFactor)
818 self.metadata[
"scaleTemplateVarianceFactor"] = varianceFactor
823 catalog = afwTable.ExposureCatalog(self.
schema)
824 catalog.reserve(sum([len(c)
for c
in catalogs]))
828 if self.config.output_image_type ==
"future":
835 if visit
is not None:
836 template.getInfo().setVisitInfo(
VisitInfo(id=visit))
839 if photoCalib
is not None and photoCalib != identity:
840 template.maskedImage = photoCalib.calibrateImage(template.maskedImage)
841 photoCalib = identity
842 template.setPhotoCalib(photoCalib)
843 template.setPsf(self.
_makePsf(template, catalog, wcs))
848 coaddInputs.ccds.extend(catalog, deep=
True)
849 template.getInfo().setCoaddInputs(coaddInputs)
850 return pipeBase.Struct(template=template, templateInputs=templateInputs)
853 """Correct the template variance plane for the effects of warping.
855 Warping introduces a covariance between neighboring pixels, while the
856 input variance plane captures only the diagonal term. Compute a
857 multiplicative correction factor to apply to the final variance so that
858 the detection significance on the convolved difference image is correct
861 Separately, also apply a scalar multiplicative correction factor to the
862 template variance if the plate scale of the coadd's constituent images
863 is different than the science image the template is being constructed
864 for. This should only be necessary if the coadd images were from a
865 different instrument than the science image, since the scale change
866 otherwise cancels between the two warps. If the plate scale of the
867 science instrument is smaller than the plate scale of the coadd
868 instrument, then the template pixels will be correlated and the true
869 variance will be higher than the image pixel noise level would suggest.
873 template : `lsst.afw.image.Exposure`
874 Assembled template; its variance plane is modified in place.
875 plateScaleFactor : `float` or `None`
876 Correction for the change in pixel area, from `_plateScaleFactor`.
881 Raised if ``plateScaleFactor`` is `None`.
883 if plateScaleFactor
is None:
885 "Could not determine the plate scale of the images the template coadds were made from,"
886 " so the template variance cannot be corrected for the change in pixel area."
888 self.metadata[
"variancePlateScaleFactor"] = plateScaleFactor
889 correlationFactor = 1.0
890 if self.config.doCorrectVarianceWarpCorrelation:
895 self.metadata[
"varianceWarpCorrelationFactor"] = correlationFactor
896 scale = correlationFactor*plateScaleFactor
899 template.variance.array *= scale
900 self.metadata[
"templateVarianceCorrectionFactor"] = scale
902 "Variance correction factor due to plate scale changes: %.4f",
906 "Variance correction factor due to warping correlations: %.4f",
910 "Overall template variance correction factor: %.4f",
915 """Compute the variance correction for the total change in pixel area
916 between the coadd's constituent images and the science image.
920 coaddInputs : `lsst.afw.image.CoaddInputs` or `None`
921 Input records of one of the coadd patches.
922 wcs : `lsst.afw.geom.SkyWcs`
923 WCS of the science image the template is being built for.
924 bbox : `lsst.geom.Box2I`
925 Bounding box of the template, used only to choose where to
926 evaluate the science image pixel scale.
930 factor : `float` or `None`
931 The correction factor equivalent to the ratio of the area of a pixel
932 from the coadd's instrument to the science instrument, or `None` if
933 this patch carries no input record from which it could be computed.
935 if coaddInputs
is None:
937 scienceScale = wcs.getPixelScale(
geom.Box2D(bbox).getCenter()).asArcseconds()
939 for record
in coaddInputs.ccds:
942 originalWcs = record.getWcs()
943 if originalWcs
is None:
945 center =
geom.Box2D(record.getBBox()).getCenter()
946 pviScale = originalWcs.getPixelScale(center).asArcseconds()
948 factor = (pviScale/scienceScale)**2
949 if np.isfinite(factor)
and factor > 0:
954 """Find the input images and epochs of each coadd in the template.
958 catalog : `lsst.afw.table.ExposureCatalog`
959 Coadds the template was built from, with their tract, patch and
961 coaddInputs : `list` [`lsst.afw.image.CoaddInputs` or `None`]
962 Input records of each coadd in ``catalog``, in the same order.
963 wcs : `lsst.afw.geom.SkyWcs`
964 WCS of the science image.
965 bbox : `lsst.geom.Box2I`
966 Bounding box of the science image.
970 templateInputs : `dict` [`tuple` [`int`, `int`], \
971 `list` [`lsst.images.DifferenceImageTemplateInput`]]
972 Inputs of each coadd that cover part of the science image, keyed
973 by tract and patch, with the part their warps cover in science
976 scienceBox = Box.from_legacy(bbox)
980 for coadd, coaddInput
in zip(catalog, coaddInputs, strict=
True):
981 coaddList = templateInputs.setdefault((coadd[
"tract"], coadd[
"patch"]), [])
982 if coaddInput
is None:
985 scienceFootprint = scienceBox.transform(scienceToCoadd).dilated_by(_FOOTPRINT_MARGIN)
986 coaddToScience = scienceToCoadd.inverted()
987 for record
in coaddInput.ccds:
988 visitInfo = record.getVisitInfo()
989 weight = record[
"weight"]
990 if visitInfo
is None or not visitInfo.getDate().isValid()
or not weight > 0:
992 key = (record[
"visit"], record[
"ccd"])
994 if key
not in regions:
995 regions[key] =
_getImageOverlap(record, coadd.getWcs(), scienceFootprint, coaddToScience,
997 region = regions[key]
1000 mjd = visitInfo.getDate().get(DateTime.MJD, DateTime.TAI)
1001 coaddList.append(DifferenceImageTemplateInput(visit=key[0], detector=key[1],
1002 mid_mjd_tai=mjd, weight=weight,
1004 return templateInputs
1007 """Set a mask plane for regions with unusually high variance.
1011 template : `lsst.afw.image.Exposure`
1012 The warped template exposure, which will be modified in place.
1014 highVarianceMaskPlaneBit = template.mask.addMaskPlane(
"HIGH_VARIANCE")
1015 ignoredPixelBits = template.mask.getPlaneBitMask(self.varianceBackground.config.ignoredPixelMask)
1016 goodMask = (template.mask.array & ignoredPixelBits) == 0
1017 goodFraction = np.count_nonzero(goodMask)/template.mask.array.size
1018 if goodFraction < self.config.highVarianceMaskFraction:
1019 self.log.info(
"Not setting HIGH_VARIANCE mask plane, only %2.1f%% of"
1020 " pixels were unmasked for background estimation, but"
1021 " %2.1f%% are required", 100*goodFraction, 100*self.config.highVarianceMaskFraction)
1023 varianceExposure = template.clone()
1024 varianceExposure.image.array = varianceExposure.variance.array
1025 varianceBackground = self.varianceBackground.
run(varianceExposure).background.getImage().array
1026 threshold = self.config.highVarianceThreshold*np.nanmedian(varianceBackground)
1027 highVariancePix = varianceBackground > threshold
1028 template.mask.array[highVariancePix] |= 2**highVarianceMaskPlaneBit
1032 """Check that the all the dataIds are from the same band and that
1033 the exposures all have the same photometric calibration, and choose
1034 the instrument of the template.
1038 dataIds : `dict` [`int`, `list` [`lsst.daf.butler.DataCoordinate`]]
1039 Record of the tract and patch of each coaddExposure.
1040 coaddExposures : `dict` [`int`, `list` of \
1041 [`lsst.daf.butler.DeferredDatasetHandle` of \
1042 `lsst.afw.image.Exposure` or
1043 `lsst.afw.image.Exposure`]]
1044 Coadds to be mosaicked.
1045 scienceInstrument : `str`, optional
1046 Instrument of the science image.
1051 Filter band of all the input exposures.
1052 photoCalib : `lsst.afw.image.PhotoCalib`
1053 Photometric calibration of all of the input exposures.
1055 ``scienceInstrument`` if any coadd was made from it, otherwise the
1056 instrument that the most coadds were made from, or ``"UNKNOWN"`` if
1057 no coadd records one. A tie goes to the instrument whose name sorts
1063 Raised if the bands or calibrations of the input exposures are not
1066 bands = set(dataId[
"band"]
for tract
in dataIds
for dataId
in dataIds[tract])
1068 raise RuntimeError(f
"GetTemplateTask called with multiple bands: {bands}")
1071 instrumentCounts = collections.Counter()
1072 for exposures
in coaddExposures.values():
1073 for exposure
in exposures:
1076 photoCalibs.append(exposure.get(component=
"photoCalib"))
1077 instrumentCounts[GetTemplateTask._getInstrument(exposure.get(component=
"coaddInputs"))] += 1
1078 if not all([photoCalibs[0] == x
for x
in photoCalibs]):
1079 msg = f
"GetTemplateTask called with exposures with different photoCalibs: {photoCalibs}"
1080 raise RuntimeError(msg)
1081 photoCalib = photoCalibs[0]
1083 del instrumentCounts[_UNKNOWN_INSTRUMENT]
1084 if scienceInstrument
in instrumentCounts:
1085 return band, photoCalib, scienceInstrument
1086 if not instrumentCounts:
1087 return band, photoCalib, _UNKNOWN_INSTRUMENT
1088 instrument = max(sorted(instrumentCounts), key=instrumentCounts.get)
1089 return band, photoCalib, instrument
1093 """Return the instrument whose observations were coadded to make a
1098 coaddInputs : `lsst.afw.image.CoaddInputs` or `None`
1099 Input records of the coadd.
1104 Instrument label of the coadd, or ``"UNKNOWN"`` if it records none.
1108 Every input of a coadd comes from the same instrument, so the first
1109 input record stands for all of them.
1111 if coaddInputs
is None or len(coaddInputs.ccds) == 0:
1112 return _UNKNOWN_INSTRUMENT
1113 visitInfo = coaddInputs.ccds[0].getVisitInfo()
1114 label = visitInfo.getInstrumentLabel()
if visitInfo
is not None else ""
1115 return label
or _UNKNOWN_INSTRUMENT
1118 """Make an exposure catalog for one tract.
1122 exposureRefs : `list` of [`lsst.daf.butler.DeferredDatasetHandle` of \
1123 `lsst.afw.image.Exposure`]
1124 Exposures to include in the catalog.
1125 dataIds : `list` [`lsst.daf.butler.DataCoordinate`]
1126 Data ids of each of the included exposures; must have "tract" and
1128 instrument : `str`, optional
1129 Instrument of the template, recorded in the catalog. Exposures
1130 made from another instrument are dropped, as are exposures that
1131 record no instrument unless ``instrument`` is ``"UNKNOWN"``.
1135 images : `dict` [`lsst.afw.image.MaskedImage`]
1136 MaskedImages of each of the input exposures, for warping.
1137 catalog : `lsst.afw.table.ExposureCatalog`
1138 Catalog of metadata for each exposure
1139 totalBox : `lsst.geom.Box2I`
1140 The union of the bounding boxes of all the input exposures.
1141 coaddInputs : `list` [`lsst.afw.image.CoaddInputs` or `None`]
1142 Input records of each exposure in ``catalog``, in the same order.
1144 catalog = afwTable.ExposureCatalog(self.
schema)
1145 catalog.reserve(len(exposureRefs))
1146 exposures = (exposureRef.get()
for exposureRef
in exposureRefs)
1149 keptCoaddInputs = []
1151 for coadd, dataId
in zip(exposures, dataIds):
1152 coaddInputs = coadd.getInfo().getCoaddInputs()
1154 if coaddInstrument != instrument:
1156 "Dropping template coadd %s: it was made from %s, but the template is made from %s.",
1162 keptCoaddInputs.append(coaddInputs)
1163 images[dataId] = coadd.maskedImage
1164 bbox = coadd.getBBox()
1165 totalBox = totalBox.expandedTo(bbox)
1166 record = catalog.addNew()
1167 record.setPsf(coadd.psf)
1168 record.setWcs(coadd.wcs)
1169 record.setPhotoCalib(coadd.photoCalib)
1170 record.setBBox(bbox)
1171 record.setValidPolygon(afwGeom.Polygon(
geom.Box2D(bbox).getCorners()))
1172 record.set(
"tract", dataId[
"tract"])
1173 record.set(
"patch", dataId[
"patch"])
1174 record.set(
"instrument", instrument)
1177 record.set(
"weight", 1)
1179 return images, catalog, totalBox, keptCoaddInputs
1182 """Merge the images that came from one tract into one larger image,
1183 ignoring NaN pixels and non-finite variance pixels from individual
1188 maskedImages : `dict` [`lsst.afw.image.MaskedImage` or
1189 `lsst.afw.image.Exposure`]
1190 Images to be merged into one larger bounding box.
1191 bbox : `lsst.geom.Box2I`
1192 Bounding box defining the image to merge into.
1193 wcs : `lsst.afw.geom.SkyWcs`
1194 WCS of all of the input images to set on the output image.
1198 merged : `lsst.afw.image.MaskedImage`
1199 Merged image with all of the inputs at their respective bbox
1202 Count of the number of good pixels (those with positive weights)
1203 in the merged image.
1204 included : `list` [`int`]
1205 List of indexes of patches that were included in the merged
1206 result, to be used to trim the exposure catalog.
1208 merged = afwImage.ExposureF(bbox, wcs)
1209 weights = afwImage.ImageF(bbox)
1211 for i, (dataId, maskedImage)
in enumerate(maskedImages.items()):
1213 clippedBox =
geom.Box2I(maskedImage.getBBox())
1214 clippedBox.clip(bbox)
1215 if clippedBox.area == 0:
1216 self.log.debug(
"%s does not overlap template region.", dataId)
1218 maskedImage = maskedImage.subset(clippedBox)
1220 good = (maskedImage.variance.array > 0) & (
1221 np.isfinite(maskedImage.variance.array)
1223 weight = maskedImage.variance.array[good] ** (-0.5)
1224 bad = np.isnan(maskedImage.image.array) | ~good
1227 maskedImage.image.array[bad] = 0.0
1228 maskedImage.variance.array[bad] = 0.0
1230 maskedImage.mask.array[bad] = 0
1234 maskedImage.image.array[good] *= weight
1235 maskedImage.variance.array[good] *= weight
1236 weights[clippedBox].array[good] += weight
1239 merged.maskedImage[clippedBox] += maskedImage
1242 good = weights.array > 0
1247 weights = weights.array[good]
1248 merged.image.array[good] /= weights
1249 merged.variance.array[good] /= weights
1251 merged.mask.array[~good] |= merged.mask.getPlaneBitMask(
"NO_DATA")
1253 return merged, good.sum(), included
1256 """Return a PSF containing the PSF at each of the input regions.
1258 Note that although this includes all the exposures from the catalog,
1259 the PSF knows which part of the template the inputs came from, so when
1260 evaluated at a given position it will not include inputs that never
1261 went in to those pixels.
1265 template : `lsst.afw.image.Exposure`
1266 Generated template the PSF is for.
1267 catalog : `lsst.afw.table.ExposureCatalog`
1268 Catalog of exposures that went into the template that contains all
1270 wcs : `lsst.afw.geom.SkyWcs`
1271 WCS of the template, to warp the PSFs to.
1275 coaddPsf : `lsst.meas.algorithms.CoaddPsf`
1276 The meta-psf constructed from all of the input catalogs.
1280 boolmask = template.mask.array & template.mask.getPlaneBitMask(
"NO_DATA") == 0
1282 centerCoord = afwGeom.SpanSet.fromMask(maskx, 1).computeCentroid()
1284 ctrl = self.config.coaddPsf.makeControl()
1285 coaddPsf = CoaddPsf(
1286 catalog, wcs, centerCoord, ctrl.warpingKernelName, ctrl.cacheSize
1291 """Return a Gaussian PSF with the width of the template CoaddPsf.
1295 template : `lsst.afw.image.Exposure`
1296 Template with the CoaddPsf to approximate. The width is measured
1297 at the average position of the PSF, or averaged over a grid of
1298 positions if the PSF cannot be evaluated there.
1302 psf : `lsst.afw.detection.GaussianPsf`
1303 Gaussian with the width of the template PSF.
1308 Raised if the PSF cannot be evaluated at any grid position.
1313 fwhm = getPsfFwhm(template.psf)
1320 self.log.info(
"Unable to evaluate PSF at the average position. "
1321 "Evaluating PSF on a grid of points.")
1322 fwhm = evaluateMeanPsfFwhm(template,
1323 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
1324 fwhmExposureGrid=self.config.fwhmExposureGrid)
1325 sigma = fwhm/gaussian_sigma_to_fwhm
1326 size = 2*int(np.ceil(_PSF_STAMP_SIGMAS*sigma)) + 1
1327 self.metadata[
"templatePsfSigma"] = float(sigma)
1328 self.log.info(
"Approximated the template PSF with a Gaussian of sigma %.3f pixels.", sigma)
1329 return afwDetection.GaussianPsf(size, size, sigma)
1333 GetTemplateConnections,
1334 dimensions=(
"instrument",
"visit",
"detector"),
1335 defaultTemplates={
"coaddName":
"dcr",
"warpTypeSuffix":
"",
"fakesType":
""},
1337 visitInfo = pipeBase.connectionTypes.Input(
1338 doc=
"VisitInfo of calexp used to determine observing conditions.",
1339 name=
"{fakesType}calexp.visitInfo",
1340 storageClass=
"VisitInfo",
1341 dimensions=(
"instrument",
"visit",
"detector"),
1343 dcrCoadds = pipeBase.connectionTypes.Input(
1344 doc=
"Input DCR template to match and subtract from the exposure",
1345 name=
"{fakesType}dcrCoadd{warpTypeSuffix}",
1346 storageClass=
"ExposureF",
1347 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
1352 def __init__(self, *, config=None):
1353 super().__init__(config=config)
1354 self.inputs.remove(
"coaddExposures")
1357class GetDcrTemplateConfig(
1358 GetTemplateConfig, pipelineConnections=GetDcrTemplateConnections
1360 """Config for GetDcrTemplateTask.
1362 Only ``output_image_type="legacy"`` is supported: the task always writes
1363 an `lsst.afw.image.ExposureF`.
1366 numSubfilters = pexConfig.Field(
1367 doc=
"Number of subfilters in the DcrCoadd.",
1371 effectiveWavelength = pexConfig.Field(
1372 doc=
"Effective wavelength of the filter in nm.",
1376 bandwidth = pexConfig.Field(
1377 doc=
"Bandwidth of the physical filter.",
1383 if self.effectiveWavelength
is None or self.bandwidth
is None:
1385 "The effective wavelength and bandwidth of the physical filter "
1386 "must be set in the getTemplate config for DCR coadds. "
1387 "Required until transmission curves are used in DM-13668."
1392 ConfigClass = GetDcrTemplateConfig
1393 _DefaultName =
"getDcrTemplate"
1395 def runQuantum(self, butlerQC, inputRefs, outputRefs):
1396 inputs = butlerQC.get(inputRefs)
1397 detector = inputs.pop(
"detector")
1398 bbox = detector.getBBox()
1399 wcs = inputs.pop(
"wcs")
1400 dcrCoaddExposureHandles = inputs.pop(
"dcrCoadds")
1401 skymap = inputs.pop(
"skyMap")
1402 visitInfo = inputs.pop(
"visitInfo")
1405 inputs.pop(
"obs_info",
None)
1408 assert not inputs,
"runQuantum got more inputs than expected"
1410 results = self.getExposures(
1411 dcrCoaddExposureHandles, bbox, skymap, wcs, visitInfo
1413 physical_filter = butlerQC.quantum.dataId[
"physical_filter"]
1415 coaddExposureHandles=results.coaddExposures,
1418 dataIds=results.dataIds,
1419 physical_filter=physical_filter,
1420 visit=outputRefs.template.dataId[
"visit"],
1421 instrument=butlerQC.quantum.dataId[
"instrument"],
1423 butlerQC.put(outputs, outputRefs)
1425 def getExposures(self, dcrCoaddExposureHandles, bbox, skymap, wcs, visitInfo):
1426 """Return lists of coadds and their corresponding dataIds that overlap
1429 The spatial index in the registry has generous padding and often
1430 supplies patches near, but not directly overlapping the detector.
1431 Filters inputs so that we don't have to read in all input coadds.
1435 dcrCoaddExposureHandles : `list` \
1436 [`lsst.daf.butler.DeferredDatasetHandle` of \
1437 `lsst.afw.image.Exposure`]
1438 Data references to exposures that might overlap the detector.
1439 bbox : `lsst.geom.Box2I`
1440 Template Bounding box of the detector geometry onto which to
1441 resample the coaddExposures.
1442 skymap : `lsst.skymap.SkyMap`
1443 Input definition of geometry/bbox and projection/wcs for
1445 wcs : `lsst.afw.geom.SkyWcs`
1446 Template WCS onto which to resample the coaddExposures.
1447 visitInfo : `lsst.afw.image.VisitInfo`
1448 Metadata for the science image.
1452 result : `lsst.pipe.base.Struct`
1453 A struct with attibutes:
1456 Dict of coadd exposures that overlap the projected bbox,
1458 (`dict` [`int`, `list` [`lsst.afw.image.Exposure`] ]).
1460 Dict of data IDs of the coadd exposures that overlap the
1461 projected bbox, indexed on tract id
1462 (`dict` [`int`, `list [`lsst.daf.butler.DataCoordinate`] ]).
1466 pipeBase.NoWorkFound
1467 Raised if no patches overlatp the input detector bbox.
1472 raise pipeBase.NoWorkFound(
"Exposure has no WCS; cannot create a template.")
1476 dataIds = collections.defaultdict(list)
1478 for coaddRef
in dcrCoaddExposureHandles:
1479 dataId = coaddRef.dataId
1480 subfilter = dataId[
"subfilter"]
1481 patchWcs = skymap[dataId[
"tract"]].getWcs()
1482 patchBBox = skymap[dataId[
"tract"]][dataId[
"patch"]].getOuterBBox()
1483 patchCorners = patchWcs.pixelToSky(
geom.Box2D(patchBBox).getCorners())
1484 patchPolygon = afwGeom.Polygon(wcs.skyToPixel(patchCorners))
1485 if patchPolygon.intersection(detectorPolygon):
1486 overlappingArea += patchPolygon.intersectionSingle(
1490 "Using template input tract=%s, patch=%s, subfilter=%s"
1491 % (dataId[
"tract"], dataId[
"patch"], dataId[
"subfilter"])
1493 if dataId[
"tract"]
in patchList:
1494 patchList[dataId[
"tract"]].append(dataId[
"patch"])
1496 patchList[dataId[
"tract"]] = [
1500 dataIds[dataId[
"tract"]].append(dataId)
1502 if not overlappingArea:
1503 raise pipeBase.NoWorkFound(
"No patches overlap detector")
1505 self.checkPatchList(patchList)
1507 coaddExposures = self.getDcrModel(patchList, dcrCoaddExposureHandles, visitInfo)
1508 return pipeBase.Struct(coaddExposures=coaddExposures, dataIds=dataIds)
1511 """Check that all of the DcrModel subfilters are present for each
1517 Dict of the patches containing valid data for each tract.
1522 If the number of exposures found for a patch does not match the
1523 number of subfilters.
1525 for tract
in patchList:
1526 for patch
in set(patchList[tract]):
1527 if patchList[tract].count(patch) != self.config.numSubfilters:
1529 "Invalid number of DcrModel subfilters found: %d vs %d expected",
1530 patchList[tract].count(patch),
1531 self.config.numSubfilters,
1535 """Build DCR-matched coadds from a list of exposure references.
1540 Dict of the patches containing valid data for each tract.
1541 coaddRefs : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
1542 Data references to `~lsst.afw.image.Exposure` representing
1543 DcrModels that overlap the detector.
1544 visitInfo : `lsst.afw.image.VisitInfo`
1545 Metadata for the science image.
1549 coaddExposures : `list` [`lsst.afw.image.Exposure`]
1550 Coadd exposures that overlap the detector.
1552 coaddExposures = collections.defaultdict(list)
1553 for tract
in patchList:
1554 for patch
in set(patchList[tract]):
1557 for coaddRef
in coaddRefs
1561 dcrModel = DcrModel.fromQuantum(
1563 self.config.effectiveWavelength,
1564 self.config.bandwidth,
1565 self.config.numSubfilters,
1567 coaddExposures[tract].append(dcrModel.buildMatchedExposureHandle(visitInfo=visitInfo))
1568 return coaddExposures
1572 condition = (coaddRef.dataId[
"tract"] == tract) & (
1573 coaddRef.dataId[
"patch"] == patch
checkHighVariance(self, template)
_clearUnmappedMaskPlanes(self, mask, planeMap)
_correctVariance(self, template, plateScaleFactor)
_getInstrument(coaddInputs)
convert_outputs_to_future(self, template, coadd_refs, detector, exposureRecord, templateInputs=None)
_makeGaussianPsf(self, template)
_plateScaleFactor(self, coaddInputs, wcs, bbox)
getExposures(self, coaddExposureHandles, bbox, skymap, wcs)
run(self, *, coaddExposureHandles, bbox, wcs, dataIds, physical_filter, visit=None, instrument=None)
_collectTemplateInputs(self, catalog, coaddInputs, wcs, bbox)
_checkInputs(dataIds, coaddExposures, scienceInstrument=None)
_makePsf(self, template, catalog, wcs)
_makeExposureCatalog(self, exposureRefs, dataIds, instrument=_UNKNOWN_INSTRUMENT)
_merge(self, maskedImages, bbox, wcs)
runQuantum(self, butlerQC, inputRefs, outputRefs)
__init__(self, *args, **kwargs)
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
_getImageOverlap(record, coaddWcs, scienceFootprint, coaddToScienceTransform, scienceBox, border)
_warpKernelSumSq(kernelName, nPhase=100)
getDcrModel(self, patchList, coaddRefs, visitInfo)
_pixelTransform(fromWcs, toWcs)
checkPatchList(self, patchList)
_selectDataRef(coaddRef, tract, patch)