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