LSST Applications 28.0.0,g1653933729+a8ce1bb630,g1a997c3884+a8ce1bb630,g28da252d5a+5bd70b7e6d,g2bbee38e9b+638fca75ac,g2bc492864f+638fca75ac,g3156d2b45e+07302053f8,g347aa1857d+638fca75ac,g35bb328faa+a8ce1bb630,g3a166c0a6a+638fca75ac,g3e281a1b8c+7bbb0b2507,g4005a62e65+17cd334064,g414038480c+5b5cd4fff3,g41af890bb2+4ffae9de63,g4e1a3235cc+0f1912dca3,g6249c6f860+3c3976f90c,g80478fca09+46aba80bd6,g82479be7b0+77990446f6,g858d7b2824+78ba4d1ce1,g89c8672015+f667a5183b,g9125e01d80+a8ce1bb630,ga5288a1d22+2a6264e9ca,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc22bb204ba+78ba4d1ce1,gc28159a63d+638fca75ac,gcf0d15dbbd+32ddb6096f,gd6b7c0dfd1+3e339405e9,gda3e153d99+78ba4d1ce1,gda6a2b7d83+32ddb6096f,gdaeeff99f8+1711a396fd,gdd5a9049c5+b18c39e5e3,ge2409df99d+a5e4577cdc,ge33fd446bb+78ba4d1ce1,ge79ae78c31+638fca75ac,gf0baf85859+64e8883e75,gf5289d68f6+e1b046a8d7,gfa443fc69c+91d9ed1ecf,gfda6b12a05+8419469a56
LSST Data Management Base Package
Loading...
Searching...
No Matches
makeWarp.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["MakeWarpTask", "MakeWarpConfig"]
23
24import logging
25import numpy
26
27import lsst.pex.config as pexConfig
28import lsst.afw.image as afwImage
29import lsst.coadd.utils as coaddUtils
30import lsst.pipe.base as pipeBase
31import lsst.pipe.base.connectionTypes as connectionTypes
32import lsst.utils as utils
33import lsst.geom
34from lsst.daf.butler import DeferredDatasetHandle
35from lsst.meas.base import DetectorVisitIdGeneratorConfig
36from lsst.meas.algorithms import CoaddPsf, CoaddPsfConfig, GaussianPsfFactory
37from lsst.skymap import BaseSkyMap
38from lsst.utils.timer import timeMethod
39from .coaddBase import CoaddBaseTask, makeSkyInfo, reorderAndPadList
40from .make_psf_matched_warp import growValidPolygons
41from .warpAndPsfMatch import WarpAndPsfMatchTask
42from collections.abc import Iterable
43
44log = logging.getLogger(__name__)
45
46
47class MakeWarpConnections(pipeBase.PipelineTaskConnections,
48 dimensions=("tract", "patch", "skymap", "instrument", "visit"),
49 defaultTemplates={"coaddName": "deep",
50 "calexpType": ""}):
51 calExpList = connectionTypes.Input(
52 doc="Input exposures to be resampled and optionally PSF-matched onto a SkyMap projection/patch",
53 name="{calexpType}calexp",
54 storageClass="ExposureF",
55 dimensions=("instrument", "visit", "detector"),
56 multiple=True,
57 deferLoad=True,
58 )
59 backgroundList = connectionTypes.Input(
60 doc="Input backgrounds to be added back into the calexp if bgSubtracted=False",
61 name="calexpBackground",
62 storageClass="Background",
63 dimensions=("instrument", "visit", "detector"),
64 multiple=True,
65 )
66 skyCorrList = connectionTypes.Input(
67 doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
68 name="skyCorr",
69 storageClass="Background",
70 dimensions=("instrument", "visit", "detector"),
71 multiple=True,
72 )
73 skyMap = connectionTypes.Input(
74 doc="Input definition of geometry/bbox and projection/wcs for warped exposures",
75 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
76 storageClass="SkyMap",
77 dimensions=("skymap",),
78 )
79 direct = connectionTypes.Output(
80 doc=("Output direct warped exposure (previously called CoaddTempExp), produced by resampling ",
81 "calexps onto the skyMap patch geometry."),
82 name="{coaddName}Coadd_directWarp",
83 storageClass="ExposureF",
84 dimensions=("tract", "patch", "skymap", "visit", "instrument"),
85 )
86 psfMatched = connectionTypes.Output(
87 doc=("Output PSF-Matched warped exposure (previously called CoaddTempExp), produced by resampling ",
88 "calexps onto the skyMap patch geometry and PSF-matching to a model PSF."),
89 name="{coaddName}Coadd_psfMatchedWarp",
90 storageClass="ExposureF",
91 dimensions=("tract", "patch", "skymap", "visit", "instrument"),
92 )
93 visitSummary = connectionTypes.Input(
94 doc="Input visit-summary catalog with updated calibration objects.",
95 name="finalVisitSummary",
96 storageClass="ExposureCatalog",
97 dimensions=("instrument", "visit",),
98 )
99
100 def __init__(self, *, config=None):
101 if config.bgSubtracted:
102 del self.backgroundList
103 if not config.doApplySkyCorr:
104 del self.skyCorrList
105 if not config.makeDirect:
106 del self.direct
107 if not config.makePsfMatched:
108 del self.psfMatched
109
110
111class MakeWarpConfig(pipeBase.PipelineTaskConfig, CoaddBaseTask.ConfigClass,
112 pipelineConnections=MakeWarpConnections):
113 """Config for MakeWarpTask."""
114
115 warpAndPsfMatch = pexConfig.ConfigurableField(
116 target=WarpAndPsfMatchTask,
117 doc="Task to warp and PSF-match calexp",
118 )
119 doWrite = pexConfig.Field(
120 doc="persist <coaddName>Coadd_<warpType>Warp",
121 dtype=bool,
122 default=True,
123 )
124 bgSubtracted = pexConfig.Field(
125 doc="Work with a background subtracted calexp?",
126 dtype=bool,
127 default=True,
128 )
129 coaddPsf = pexConfig.ConfigField(
130 doc="Configuration for CoaddPsf",
131 dtype=CoaddPsfConfig,
132 )
133 makeDirect = pexConfig.Field(
134 doc="Make direct Warp/Coadds",
135 dtype=bool,
136 default=True,
137 )
138 makePsfMatched = pexConfig.Field(
139 doc="Make Psf-Matched Warp/Coadd?",
140 dtype=bool,
141 default=False,
142 )
143 modelPsf = GaussianPsfFactory.makeField(doc="Model Psf factory")
144 useVisitSummaryPsf = pexConfig.Field(
145 doc=(
146 "If True, use the PSF model and aperture corrections from the 'visitSummary' connection. "
147 "If False, use the PSF model and aperture corrections from the 'exposure' connection. "
148 ),
149 dtype=bool,
150 default=True,
151 )
152 doWriteEmptyWarps = pexConfig.Field(
153 dtype=bool,
154 default=False,
155 doc="Write out warps even if they are empty"
156 )
157 hasFakes = pexConfig.Field(
158 doc="Should be set to True if fake sources have been inserted into the input data.",
159 dtype=bool,
160 default=False,
161 )
162 doApplySkyCorr = pexConfig.Field(
163 dtype=bool,
164 default=False,
165 doc="Apply sky correction?",
166 )
167 idGenerator = DetectorVisitIdGeneratorConfig.make_field()
168
169 def validate(self):
170 CoaddBaseTask.ConfigClass.validate(self)
171
172 if not self.makePsfMatched and not self.makeDirect:
173 raise ValueError("At least one of config.makePsfMatched and config.makeDirect must be True")
174 if self.warpAndPsfMatch.warp.cacheSize != self.coaddPsf.cacheSize:
175 # This is an incomplete check: usually the CoaddPsf cache size
176 # configured here in MakeWarpTask is superseded by the one in
177 # AssembleCoaddTask. A pipeline contract in the drp_pipe is
178 # present to check that.
179 raise ValueError("Image warping cache size and CoaddPSf warping cache size do not agree.")
180
181 if self.matchingKernelSize:
182 if self.matchingKernelSize != self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize:
183 raise pexConfig.FieldValidationError(
184 self.__class__.matchingKernelSize, self,
185 f"matchingKernelSize ({self.matchingKernelSize}) must match "
186 "warpAndPsfMatch.psfMatch.kernel.active.kernelSize "
187 f"({self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize})"
188 )
189
190 def setDefaults(self):
191 CoaddBaseTask.ConfigClass.setDefaults(self)
192 self.warpAndPsfMatch.warp.cacheSize = 0
193 self.coaddPsf.cacheSize = 0
194 self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize = self.matchingKernelSize
195
196
197class MakeWarpTask(CoaddBaseTask):
198 """Warp and optionally PSF-Match calexps onto an a common projection.
199
200 Warp and optionally PSF-Match calexps onto a common projection, by
201 performing the following operations:
202 - Group calexps by visit/run
203 - For each visit, generate a Warp by calling method @ref run.
204 `run` loops over the visit's calexps calling
205 `~lsst.pipe.tasks.warpAndPsfMatch.WarpAndPsfMatchTask` on each visit
206
207 """
208 ConfigClass = MakeWarpConfig
209 _DefaultName = "makeWarp"
210
211 def __init__(self, **kwargs):
212 CoaddBaseTask.__init__(self, **kwargs)
213 self.makeSubtask("warpAndPsfMatch")
214 if self.config.hasFakes:
215 self.calexpType = "fakes_calexp"
216 else:
217 self.calexpType = "calexp"
218
219 @utils.inheritDoc(pipeBase.PipelineTask)
220 def runQuantum(self, butlerQC, inputRefs, outputRefs):
221 # Docstring to be augmented with info from PipelineTask.runQuantum
222 """Notes
223 -----
224 Obtain the list of input detectors from calExpList. Sort them by
225 detector order (to ensure reproducibility). Then ensure all input
226 lists are in the same sorted detector order.
227 """
228 detectorOrder = [ref.datasetRef.dataId['detector'] for ref in inputRefs.calExpList]
229 detectorOrder.sort()
230 inputRefs = reorderRefs(inputRefs, detectorOrder, dataIdKey='detector')
231
232 # Read in all inputs.
233 inputs = butlerQC.get(inputRefs)
234
235 # Construct skyInfo expected by `run`. We remove the SkyMap itself
236 # from the dictionary so we can pass it as kwargs later.
237 skyMap = inputs.pop("skyMap")
238 quantumDataId = butlerQC.quantum.dataId
239 skyInfo = makeSkyInfo(skyMap, tractId=quantumDataId['tract'], patchId=quantumDataId['patch'])
240
241 # Construct list of input DataIds expected by `run`.
242 dataIdList = [ref.datasetRef.dataId for ref in inputRefs.calExpList]
243 # Construct list of packed integer IDs expected by `run`.
244 ccdIdList = [
245 self.config.idGenerator.apply(dataId).catalog_id
246 for dataId in dataIdList
247 ]
248
249 # Check early that the visitSummary contains everything we need.
250 visitSummary = inputs["visitSummary"]
251 bboxList = []
252 wcsList = []
253 for dataId in dataIdList:
254 row = visitSummary.find(dataId["detector"])
255 if row is None:
256 raise RuntimeError(
257 f"Unexpectedly incomplete visitSummary provided to makeWarp: {dataId} is missing."
258 )
259 bboxList.append(row.getBBox())
260 wcsList.append(row.getWcs())
261 inputs["bboxList"] = bboxList
262 inputs["wcsList"] = wcsList
263
264 # Do an initial selection on inputs with complete wcs/photoCalib info.
265 # Qualifying calexps will be read in the following call.
266 completeIndices = self._prepareCalibratedExposures(**inputs)
267 inputs = self.filterInputs(indices=completeIndices, inputs=inputs)
268
269 # Do another selection based on the configured selection task
270 # (using updated WCSs to determine patch overlap if an external
271 # calibration was applied).
272 cornerPosList = lsst.geom.Box2D(skyInfo.bbox).getCorners()
273 coordList = [skyInfo.wcs.pixelToSky(pos) for pos in cornerPosList]
274 goodIndices = self.select.run(**inputs, coordList=coordList, dataIds=dataIdList)
275 inputs = self.filterInputs(indices=goodIndices, inputs=inputs)
276
277 # Extract integer visitId requested by `run`.
278 visitId = dataIdList[0]["visit"]
279
280 results = self.run(**inputs,
281 visitId=visitId,
282 ccdIdList=[ccdIdList[i] for i in goodIndices],
283 dataIdList=[dataIdList[i] for i in goodIndices],
284 skyInfo=skyInfo)
285 if self.config.makeDirect and results.exposures["direct"] is not None:
286 butlerQC.put(results.exposures["direct"], outputRefs.direct)
287 if self.config.makePsfMatched and results.exposures["psfMatched"] is not None:
288 butlerQC.put(results.exposures["psfMatched"], outputRefs.psfMatched)
289
290 @timeMethod
291 def run(self, calExpList, ccdIdList, skyInfo, visitId=0, dataIdList=None, **kwargs):
292 """Create a Warp from inputs.
293
294 We iterate over the multiple calexps in a single exposure to construct
295 the warp (previously called a coaddTempExp) of that exposure to the
296 supplied tract/patch.
297
298 Pixels that receive no pixels are set to NAN; this is not correct
299 (violates LSST algorithms group policy), but will be fixed up by
300 interpolating after the coaddition.
301
302 calexpRefList : `list`
303 List of data references for calexps that (may)
304 overlap the patch of interest.
305 skyInfo : `lsst.pipe.base.Struct`
306 Struct from `~lsst.pipe.base.coaddBase.makeSkyInfo()` with
307 geometric information about the patch.
308 visitId : `int`
309 Integer identifier for visit, for the table that will
310 produce the CoaddPsf.
311
312 Returns
313 -------
314 result : `lsst.pipe.base.Struct`
315 Results as a struct with attributes:
316
317 ``exposures``
318 A dictionary containing the warps requested:
319 "direct": direct warp if ``config.makeDirect``
320 "psfMatched": PSF-matched warp if ``config.makePsfMatched``
321 (`dict`).
322 """
323 warpTypeList = self.getWarpTypeList()
324
325 totGoodPix = {warpType: 0 for warpType in warpTypeList}
326 didSetMetadata = {warpType: False for warpType in warpTypeList}
327 warps = {warpType: self._prepareEmptyExposure(skyInfo) for warpType in warpTypeList}
328 inputRecorder = {warpType: self.inputRecorder.makeCoaddTempExpRecorder(visitId, len(calExpList))
329 for warpType in warpTypeList}
330
331 modelPsf = self.config.modelPsf.apply() if self.config.makePsfMatched else None
332 if dataIdList is None:
333 dataIdList = ccdIdList
334
335 for calExpInd, (calExp, ccdId, dataId) in enumerate(zip(calExpList, ccdIdList, dataIdList)):
336 self.log.info("Processing calexp %d of %d for this Warp: id=%s",
337 calExpInd+1, len(calExpList), dataId)
338 try:
339 warpedAndMatched = self.warpAndPsfMatch.run(calExp, modelPsf=modelPsf,
340 wcs=skyInfo.wcs, maxBBox=skyInfo.bbox,
341 makeDirect=self.config.makeDirect,
342 makePsfMatched=self.config.makePsfMatched)
343 except Exception as e:
344 self.log.warning("WarpAndPsfMatch failed for calexp %s; skipping it: %s", dataId, e)
345 continue
346 try:
347 numGoodPix = {warpType: 0 for warpType in warpTypeList}
348 for warpType in warpTypeList:
349 exposure = warpedAndMatched.getDict()[warpType]
350 if exposure is None:
351 continue
352 warp = warps[warpType]
353 if didSetMetadata[warpType]:
354 mimg = exposure.getMaskedImage()
355 mimg *= (warp.getPhotoCalib().getInstFluxAtZeroMagnitude()
356 / exposure.getPhotoCalib().getInstFluxAtZeroMagnitude())
357 del mimg
358 numGoodPix[warpType] = coaddUtils.copyGoodPixels(
359 warp.getMaskedImage(), exposure.getMaskedImage(), self.getBadPixelMask())
360 totGoodPix[warpType] += numGoodPix[warpType]
361 self.log.debug("Calexp %s has %d good pixels in this patch (%.1f%%) for %s",
362 dataId, numGoodPix[warpType],
363 100.0*numGoodPix[warpType]/skyInfo.bbox.getArea(), warpType)
364 if numGoodPix[warpType] > 0 and not didSetMetadata[warpType]:
365 warp.info.id = exposure.info.id
366 warp.setPhotoCalib(exposure.getPhotoCalib())
367 warp.setFilter(exposure.getFilter())
368 warp.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
369 # PSF replaced with CoaddPsf after loop if and only if
370 # creating direct warp.
371 warp.setPsf(exposure.getPsf())
372 didSetMetadata[warpType] = True
373
374 # Need inputRecorder for CoaddApCorrMap for both direct and
375 # PSF-matched.
376 inputRecorder[warpType].addCalExp(calExp, ccdId, numGoodPix[warpType])
377
378 except Exception as e:
379 self.log.warning("Error processing calexp %s; skipping it: %s", dataId, e)
380 continue
381
382 for warpType in warpTypeList:
383 self.log.info("%sWarp has %d good pixels (%.1f%%)",
384 warpType, totGoodPix[warpType], 100.0*totGoodPix[warpType]/skyInfo.bbox.getArea())
385
386 if totGoodPix[warpType] > 0 and didSetMetadata[warpType]:
387 inputRecorder[warpType].finish(warps[warpType], totGoodPix[warpType])
388 if warpType == "direct":
389 warps[warpType].setPsf(
390 CoaddPsf(inputRecorder[warpType].coaddInputs.ccds, skyInfo.wcs,
391 self.config.coaddPsf.makeControl()))
392 else: # warpType == "psfMached"
393 growValidPolygons(
394 inputRecorder[warpType].coaddInputs,
395 -self.config.warpAndPsfMatch.psfMatch.kernel.active.kernelSize // 2,
396 )
397 else:
398 if not self.config.doWriteEmptyWarps:
399 # No good pixels. Exposure still empty.
400 warps[warpType] = None
401 # NoWorkFound is unnecessary as the downstream tasks will
402 # adjust the quantum accordingly.
403
404 result = pipeBase.Struct(exposures=warps)
405 return result
406
407 def filterInputs(self, indices, inputs):
408 """Filter task inputs by their indices.
409
410 Parameters
411 ----------
412 indices : `list` [`int`]
413 inputs : `dict` [`list`]
414 A dictionary of input connections to be passed to run.
415
416 Returns
417 -------
418 inputs : `dict` [`list`]
419 Task inputs with their lists filtered by indices.
420 """
421 for key in inputs.keys():
422 # Only down-select on list inputs
423 if isinstance(inputs[key], list):
424 inputs[key] = [inputs[key][ind] for ind in indices]
425 return inputs
426
427 def _prepareCalibratedExposures(self, *, visitSummary, calExpList=[], wcsList=None,
428 backgroundList=None, skyCorrList=None, **kwargs):
429 """Calibrate and add backgrounds to input calExpList in place.
430
431 Parameters
432 ----------
433 visitSummary : `lsst.afw.table.ExposureCatalog`
434 Exposure catalog with potentially all calibrations. Attributes set
435 to `None` are ignored.
436 calExpList : `list` [`lsst.afw.image.Exposure` or
437 `lsst.daf.butler.DeferredDatasetHandle`]
438 Sequence of calexps to be modified in place.
439 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
440 The WCSs of the calexps in ``calExpList``. These will be used to
441 determine if the calexp should be used in the warp. The list is
442 dynamically updated with the WCSs from the visitSummary.
443 backgroundList : `list` [`lsst.afw.math.backgroundList`], optional
444 Sequence of backgrounds to be added back in if bgSubtracted=False.
445 skyCorrList : `list` [`lsst.afw.math.backgroundList`], optional
446 Sequence of background corrections to be subtracted if
447 doApplySkyCorr=True.
448 **kwargs
449 Additional keyword arguments.
450
451 Returns
452 -------
453 indices : `list` [`int`]
454 Indices of ``calExpList`` and friends that have valid
455 photoCalib/skyWcs.
456 """
457 wcsList = len(calExpList)*[None] if wcsList is None else wcsList
458 backgroundList = len(calExpList)*[None] if backgroundList is None else backgroundList
459 skyCorrList = len(calExpList)*[None] if skyCorrList is None else skyCorrList
460
461 includeCalibVar = self.config.includeCalibVar
462
463 indices = []
464 for index, (calexp, background, skyCorr) in enumerate(zip(calExpList,
465 backgroundList,
466 skyCorrList)):
467 if isinstance(calexp, DeferredDatasetHandle):
468 calexp = calexp.get()
469
470 if not self.config.bgSubtracted:
471 calexp.maskedImage += background.getImage()
472
473 detectorId = calexp.info.getDetector().getId()
474
475 # Load all calibrations from visitSummary.
476 row = visitSummary.find(detectorId)
477 if row is None:
478 raise RuntimeError(
479 f"Unexpectedly incomplete visitSummary: detector={detectorId} is missing."
480 )
481 if (photoCalib := row.getPhotoCalib()) is not None:
482 calexp.setPhotoCalib(photoCalib)
483 else:
484 self.log.warning(
485 "Detector id %d for visit %d has None for photoCalib in the visitSummary and will "
486 "not be used in the warp", detectorId, row["visit"],
487 )
488 continue
489 if (skyWcs := row.getWcs()) is not None:
490 calexp.setWcs(skyWcs)
491 wcsList[index] = skyWcs
492 else:
493 self.log.warning(
494 "Detector id %d for visit %d has None for wcs in the visitSummary and will "
495 "not be used in the warp", detectorId, row["visit"],
496 )
497 continue
498 if self.config.useVisitSummaryPsf:
499 if (psf := row.getPsf()) is not None:
500 calexp.setPsf(psf)
501 else:
502 self.log.warning(
503 "Detector id %d for visit %d has None for psf in the visitSummary and will "
504 "not be used in the warp", detectorId, row["visit"],
505 )
506 continue
507 if (apCorrMap := row.getApCorrMap()) is not None:
508 calexp.info.setApCorrMap(apCorrMap)
509 else:
510 self.log.warning(
511 "Detector id %d for visit %d has None for apCorrMap in the visitSummary and will "
512 "not be used in the warp", detectorId, row["visit"],
513 )
514 continue
515 else:
516 if calexp.getPsf() is None:
517 self.log.warning(
518 "Detector id %d for visit %d has None for psf for the calexp and will "
519 "not be used in the warp", detectorId, row["visit"],
520 )
521 continue
522 if calexp.info.getApCorrMap() is None:
523 self.log.warning(
524 "Detector id %d for visit %d has None for apCorrMap in the calexp and will "
525 "not be used in the warp", detectorId, row["visit"],
526 )
527 continue
528
529 # Calibrate the image.
530 calexp.maskedImage = photoCalib.calibrateImage(calexp.maskedImage,
531 includeScaleUncertainty=includeCalibVar)
532 calexp.maskedImage /= photoCalib.getCalibrationMean()
533 # TODO: The images will have a calibration of 1.0 everywhere once
534 # RFC-545 is implemented.
535 # exposure.setCalib(afwImage.Calib(1.0))
536
537 # Apply skycorr
538 if self.config.doApplySkyCorr:
539 calexp.maskedImage -= skyCorr.getImage()
540
541 indices.append(index)
542 calExpList[index] = calexp
543
544 return indices
545
546 @staticmethod
547 def _prepareEmptyExposure(skyInfo):
548 """Produce an empty exposure for a given patch.
549
550 Parameters
551 ----------
552 skyInfo : `lsst.pipe.base.Struct`
553 Struct from `~lsst.pipe.base.coaddBase.makeSkyInfo()` with
554 geometric information about the patch.
555
556 Returns
557 -------
558 exp : `lsst.afw.image.exposure.ExposureF`
559 An empty exposure for a given patch.
560 """
561 exp = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
562 exp.getMaskedImage().set(numpy.nan, afwImage.Mask
563 .getPlaneBitMask("NO_DATA"), numpy.inf)
564 return exp
565
566 def getWarpTypeList(self):
567 """Return list of requested warp types per the config.
568 """
569 warpTypeList = []
570 if self.config.makeDirect:
571 warpTypeList.append("direct")
572 if self.config.makePsfMatched:
573 warpTypeList.append("psfMatched")
574 return warpTypeList
575
576
577def reorderRefs(inputRefs, outputSortKeyOrder, dataIdKey):
578 """Reorder inputRefs per outputSortKeyOrder.
579
580 Any inputRefs which are lists will be resorted per specified key e.g.,
581 'detector.' Only iterables will be reordered, and values can be of type
582 `lsst.pipe.base.connections.DeferredDatasetRef` or
583 `lsst.daf.butler.core.datasets.ref.DatasetRef`.
584
585 Returned lists of refs have the same length as the outputSortKeyOrder.
586 If an outputSortKey not in the inputRef, then it will be padded with None.
587 If an inputRef contains an inputSortKey that is not in the
588 outputSortKeyOrder it will be removed.
589
590 Parameters
591 ----------
592 inputRefs : `lsst.pipe.base.connections.QuantizedConnection`
593 Input references to be reordered and padded.
594 outputSortKeyOrder : `iterable`
595 Iterable of values to be compared with inputRef's dataId[dataIdKey].
596 dataIdKey : `str`
597 The data ID key in the dataRefs to compare with the outputSortKeyOrder.
598
599 Returns
600 -------
601 inputRefs : `lsst.pipe.base.connections.QuantizedConnection`
602 Quantized Connection with sorted DatasetRef values sorted if iterable.
603 """
604 for connectionName, refs in inputRefs:
605 if isinstance(refs, Iterable):
606 if hasattr(refs[0], "dataId"):
607 inputSortKeyOrder = [ref.dataId[dataIdKey] for ref in refs]
608 else:
609 inputSortKeyOrder = [ref.datasetRef.dataId[dataIdKey] for ref in refs]
610 if inputSortKeyOrder != outputSortKeyOrder:
611 setattr(inputRefs, connectionName,
612 reorderAndPadList(refs, inputSortKeyOrder, outputSortKeyOrder))
613 return inputRefs
Represent a 2-dimensional array of bitmask pixels.
Definition Mask.h:77
A floating-point coordinate rectangle geometry.
Definition Box.h:413
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Definition CoaddPsf.h:58
int copyGoodPixels(lsst::afw::image::Image< ImagePixelT > &destImage, lsst::afw::image::Image< ImagePixelT > const &srcImage)
copy good pixels from one image to another