Coverage for python/lsst/pipe/tasks/makeWarp.py: 16%

263 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-12 01:27 -0700

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22import logging 

23import numpy 

24 

25import lsst.pex.config as pexConfig 

26import lsst.afw.image as afwImage 

27import lsst.coadd.utils as coaddUtils 

28import lsst.pipe.base as pipeBase 

29import lsst.pipe.base.connectionTypes as connectionTypes 

30import lsst.utils as utils 

31import lsst.geom 

32from lsst.meas.algorithms import CoaddPsf, CoaddPsfConfig 

33from lsst.skymap import BaseSkyMap 

34from lsst.utils.timer import timeMethod 

35from .coaddBase import CoaddBaseTask, makeSkyInfo, reorderAndPadList 

36from .warpAndPsfMatch import WarpAndPsfMatchTask 

37from collections.abc import Iterable 

38 

39__all__ = ["MakeWarpTask", "MakeWarpConfig"] 

40 

41log = logging.getLogger(__name__) 

42 

43 

44class MakeWarpConnections(pipeBase.PipelineTaskConnections, 

45 dimensions=("tract", "patch", "skymap", "instrument", "visit"), 

46 defaultTemplates={"coaddName": "deep", 

47 "skyWcsName": "jointcal", 

48 "photoCalibName": "fgcm", 

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 externalSkyWcsTractCatalog = connectionTypes.Input( 

79 doc=("Per-tract, per-visit wcs calibrations. These catalogs use the detector " 

80 "id for the catalog id, sorted on id for fast lookup."), 

81 name="{skyWcsName}SkyWcsCatalog", 

82 storageClass="ExposureCatalog", 

83 dimensions=("instrument", "visit", "tract"), 

84 ) 

85 externalSkyWcsGlobalCatalog = connectionTypes.Input( 

86 doc=("Per-visit wcs calibrations computed globally (with no tract information). " 

87 "These catalogs use the detector id for the catalog id, sorted on id for " 

88 "fast lookup."), 

89 name="{skyWcsName}SkyWcsCatalog", 

90 storageClass="ExposureCatalog", 

91 dimensions=("instrument", "visit"), 

92 ) 

93 externalPhotoCalibTractCatalog = connectionTypes.Input( 

94 doc=("Per-tract, per-visit photometric calibrations. These catalogs use the " 

95 "detector id for the catalog id, sorted on id for fast lookup."), 

96 name="{photoCalibName}PhotoCalibCatalog", 

97 storageClass="ExposureCatalog", 

98 dimensions=("instrument", "visit", "tract"), 

99 ) 

100 externalPhotoCalibGlobalCatalog = connectionTypes.Input( 

101 doc=("Per-visit photometric calibrations computed globally (with no tract " 

102 "information). These catalogs use the detector id for the catalog id, " 

103 "sorted on id for fast lookup."), 

104 name="{photoCalibName}PhotoCalibCatalog", 

105 storageClass="ExposureCatalog", 

106 dimensions=("instrument", "visit"), 

107 ) 

108 finalizedPsfApCorrCatalog = connectionTypes.Input( 

109 doc=("Per-visit finalized psf models and aperture correction maps. " 

110 "These catalogs use the detector id for the catalog id, " 

111 "sorted on id for fast lookup."), 

112 name="finalized_psf_ap_corr_catalog", 

113 storageClass="ExposureCatalog", 

114 dimensions=("instrument", "visit"), 

115 ) 

116 direct = connectionTypes.Output( 

117 doc=("Output direct warped exposure (previously called CoaddTempExp), produced by resampling ", 

118 "calexps onto the skyMap patch geometry."), 

119 name="{coaddName}Coadd_directWarp", 

120 storageClass="ExposureF", 

121 dimensions=("tract", "patch", "skymap", "visit", "instrument"), 

122 ) 

123 psfMatched = connectionTypes.Output( 

124 doc=("Output PSF-Matched warped exposure (previously called CoaddTempExp), produced by resampling ", 

125 "calexps onto the skyMap patch geometry and PSF-matching to a model PSF."), 

126 name="{coaddName}Coadd_psfMatchedWarp", 

127 storageClass="ExposureF", 

128 dimensions=("tract", "patch", "skymap", "visit", "instrument"), 

129 ) 

130 # TODO DM-28769, have selectImages subtask indicate which connections they need: 

131 wcsList = connectionTypes.Input( 

132 doc="WCSs of calexps used by SelectImages subtask to determine if the calexp overlaps the patch", 

133 name="{calexpType}calexp.wcs", 

134 storageClass="Wcs", 

135 dimensions=("instrument", "visit", "detector"), 

136 multiple=True, 

137 ) 

138 bboxList = connectionTypes.Input( 

139 doc="BBoxes of calexps used by SelectImages subtask to determine if the calexp overlaps the patch", 

140 name="{calexpType}calexp.bbox", 

141 storageClass="Box2I", 

142 dimensions=("instrument", "visit", "detector"), 

143 multiple=True, 

144 ) 

145 visitSummary = connectionTypes.Input( 

146 doc="Consolidated exposure metadata from ConsolidateVisitSummaryTask", 

147 name="{calexpType}visitSummary", 

148 storageClass="ExposureCatalog", 

149 dimensions=("instrument", "visit",), 

150 ) 

151 

152 def __init__(self, *, config=None): 

153 super().__init__(config=config) 

154 if config.bgSubtracted: 

155 self.inputs.remove("backgroundList") 

156 if not config.doApplySkyCorr: 

157 self.inputs.remove("skyCorrList") 

158 if config.doApplyExternalSkyWcs: 

159 if config.useGlobalExternalSkyWcs: 

160 self.inputs.remove("externalSkyWcsTractCatalog") 

161 else: 

162 self.inputs.remove("externalSkyWcsGlobalCatalog") 

163 else: 

164 self.inputs.remove("externalSkyWcsTractCatalog") 

165 self.inputs.remove("externalSkyWcsGlobalCatalog") 

166 if config.doApplyExternalPhotoCalib: 

167 if config.useGlobalExternalPhotoCalib: 

168 self.inputs.remove("externalPhotoCalibTractCatalog") 

169 else: 

170 self.inputs.remove("externalPhotoCalibGlobalCatalog") 

171 else: 

172 self.inputs.remove("externalPhotoCalibTractCatalog") 

173 self.inputs.remove("externalPhotoCalibGlobalCatalog") 

174 if not config.doApplyFinalizedPsf: 

175 self.inputs.remove("finalizedPsfApCorrCatalog") 

176 if not config.makeDirect: 

177 self.outputs.remove("direct") 

178 if not config.makePsfMatched: 

179 self.outputs.remove("psfMatched") 

180 # TODO DM-28769: add connection per selectImages connections 

181 if config.select.target != lsst.pipe.tasks.selectImages.PsfWcsSelectImagesTask: 

182 self.inputs.remove("visitSummary") 

183 

184 

185class MakeWarpConfig(pipeBase.PipelineTaskConfig, CoaddBaseTask.ConfigClass, 

186 pipelineConnections=MakeWarpConnections): 

187 """Config for MakeWarpTask.""" 

188 warpAndPsfMatch = pexConfig.ConfigurableField( 

189 target=WarpAndPsfMatchTask, 

190 doc="Task to warp and PSF-match calexp", 

191 ) 

192 doWrite = pexConfig.Field( 

193 doc="persist <coaddName>Coadd_<warpType>Warp", 

194 dtype=bool, 

195 default=True, 

196 ) 

197 bgSubtracted = pexConfig.Field( 

198 doc="Work with a background subtracted calexp?", 

199 dtype=bool, 

200 default=True, 

201 ) 

202 coaddPsf = pexConfig.ConfigField( 

203 doc="Configuration for CoaddPsf", 

204 dtype=CoaddPsfConfig, 

205 ) 

206 makeDirect = pexConfig.Field( 

207 doc="Make direct Warp/Coadds", 

208 dtype=bool, 

209 default=True, 

210 ) 

211 makePsfMatched = pexConfig.Field( 

212 doc="Make Psf-Matched Warp/Coadd?", 

213 dtype=bool, 

214 default=False, 

215 ) 

216 doWriteEmptyWarps = pexConfig.Field( 

217 dtype=bool, 

218 default=False, 

219 doc="Write out warps even if they are empty" 

220 ) 

221 hasFakes = pexConfig.Field( 

222 doc="Should be set to True if fake sources have been inserted into the input data.", 

223 dtype=bool, 

224 default=False, 

225 ) 

226 doApplySkyCorr = pexConfig.Field( 

227 dtype=bool, 

228 default=False, 

229 doc="Apply sky correction?", 

230 ) 

231 doApplyFinalizedPsf = pexConfig.Field( 

232 doc="Whether to apply finalized psf models and aperture correction map.", 

233 dtype=bool, 

234 default=True, 

235 ) 

236 

237 def validate(self): 

238 CoaddBaseTask.ConfigClass.validate(self) 

239 

240 if not self.makePsfMatched and not self.makeDirect: 

241 raise RuntimeError("At least one of config.makePsfMatched and config.makeDirect must be True") 

242 if self.doPsfMatch: 

243 # Backwards compatibility. 

244 log.warning("Config doPsfMatch deprecated. Setting makePsfMatched=True and makeDirect=False") 

245 self.makePsfMatched = True 

246 self.makeDirect = False 

247 

248 def setDefaults(self): 

249 CoaddBaseTask.ConfigClass.setDefaults(self) 

250 self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize = self.matchingKernelSize 

251 

252 

253class MakeWarpTask(CoaddBaseTask): 

254 """Warp and optionally PSF-Match calexps onto an a common projection 

255 """ 

256 ConfigClass = MakeWarpConfig 

257 _DefaultName = "makeWarp" 

258 

259 def __init__(self, **kwargs): 

260 CoaddBaseTask.__init__(self, **kwargs) 

261 self.makeSubtask("warpAndPsfMatch") 

262 if self.config.hasFakes: 

263 self.calexpType = "fakes_calexp" 

264 else: 

265 self.calexpType = "calexp" 

266 

267 @utils.inheritDoc(pipeBase.PipelineTask) 

268 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

269 """ 

270 Notes 

271 ---- 

272 Construct warps for requested warp type for single epoch 

273 """ 

274 # Obtain the list of input detectors from calExpList. Sort them by 

275 # detector order (to ensure reproducibility). Then ensure all input 

276 # lists are in the same sorted detector order. 

277 detectorOrder = [ref.datasetRef.dataId['detector'] for ref in inputRefs.calExpList] 

278 detectorOrder.sort() 

279 inputRefs = reorderRefs(inputRefs, detectorOrder, dataIdKey='detector') 

280 

281 # Read in all inputs. 

282 inputs = butlerQC.get(inputRefs) 

283 

284 # Construct skyInfo expected by `run`. We remove the SkyMap itself 

285 # from the dictionary so we can pass it as kwargs later. 

286 skyMap = inputs.pop("skyMap") 

287 quantumDataId = butlerQC.quantum.dataId 

288 skyInfo = makeSkyInfo(skyMap, tractId=quantumDataId['tract'], patchId=quantumDataId['patch']) 

289 

290 # Construct list of input DataIds expected by `run` 

291 dataIdList = [ref.datasetRef.dataId for ref in inputRefs.calExpList] 

292 # Construct list of packed integer IDs expected by `run` 

293 ccdIdList = [dataId.pack("visit_detector") for dataId in dataIdList] 

294 

295 # Run the selector and filter out calexps that were not selected 

296 # primarily because they do not overlap the patch 

297 cornerPosList = lsst.geom.Box2D(skyInfo.bbox).getCorners() 

298 coordList = [skyInfo.wcs.pixelToSky(pos) for pos in cornerPosList] 

299 goodIndices = self.select.run(**inputs, coordList=coordList, dataIds=dataIdList) 

300 inputs = self.filterInputs(indices=goodIndices, inputs=inputs) 

301 

302 # Read from disk only the selected calexps 

303 inputs['calExpList'] = [ref.get() for ref in inputs['calExpList']] 

304 

305 # Extract integer visitId requested by `run` 

306 visits = [dataId['visit'] for dataId in dataIdList] 

307 visitId = visits[0] 

308 

309 if self.config.doApplyExternalSkyWcs: 

310 if self.config.useGlobalExternalSkyWcs: 

311 externalSkyWcsCatalog = inputs.pop("externalSkyWcsGlobalCatalog") 

312 else: 

313 externalSkyWcsCatalog = inputs.pop("externalSkyWcsTractCatalog") 

314 else: 

315 externalSkyWcsCatalog = None 

316 

317 if self.config.doApplyExternalPhotoCalib: 

318 if self.config.useGlobalExternalPhotoCalib: 

319 externalPhotoCalibCatalog = inputs.pop("externalPhotoCalibGlobalCatalog") 

320 else: 

321 externalPhotoCalibCatalog = inputs.pop("externalPhotoCalibTractCatalog") 

322 else: 

323 externalPhotoCalibCatalog = None 

324 

325 if self.config.doApplyFinalizedPsf: 

326 finalizedPsfApCorrCatalog = inputs.pop("finalizedPsfApCorrCatalog") 

327 else: 

328 finalizedPsfApCorrCatalog = None 

329 

330 completeIndices = self.prepareCalibratedExposures(**inputs, 

331 externalSkyWcsCatalog=externalSkyWcsCatalog, 

332 externalPhotoCalibCatalog=externalPhotoCalibCatalog, 

333 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog) 

334 # Redo the input selection with inputs with complete wcs/photocalib info. 

335 inputs = self.filterInputs(indices=completeIndices, inputs=inputs) 

336 

337 results = self.run(**inputs, visitId=visitId, 

338 ccdIdList=[ccdIdList[i] for i in goodIndices], 

339 dataIdList=[dataIdList[i] for i in goodIndices], 

340 skyInfo=skyInfo) 

341 if self.config.makeDirect and results.exposures["direct"] is not None: 

342 butlerQC.put(results.exposures["direct"], outputRefs.direct) 

343 if self.config.makePsfMatched and results.exposures["psfMatched"] is not None: 

344 butlerQC.put(results.exposures["psfMatched"], outputRefs.psfMatched) 

345 

346 @timeMethod 

347 def run(self, calExpList, ccdIdList, skyInfo, visitId=0, dataIdList=None, **kwargs): 

348 """Create a Warp from inputs 

349 

350 We iterate over the multiple calexps in a single exposure to construct 

351 the warp (previously called a coaddTempExp) of that exposure to the 

352 supplied tract/patch. 

353 

354 Pixels that receive no pixels are set to NAN; this is not correct 

355 (violates LSST algorithms group policy), but will be fixed up by 

356 interpolating after the coaddition. 

357 

358 @param calexpRefList: List of data references for calexps that (may) 

359 overlap the patch of interest 

360 @param skyInfo: Struct from CoaddBaseTask.getSkyInfo() with geometric 

361 information about the patch 

362 @param visitId: integer identifier for visit, for the table that will 

363 produce the CoaddPsf 

364 @return a pipeBase Struct containing: 

365 - exposures: a dictionary containing the warps requested: 

366 "direct": direct warp if config.makeDirect 

367 "psfMatched": PSF-matched warp if config.makePsfMatched 

368 """ 

369 warpTypeList = self.getWarpTypeList() 

370 

371 totGoodPix = {warpType: 0 for warpType in warpTypeList} 

372 didSetMetadata = {warpType: False for warpType in warpTypeList} 

373 warps = {warpType: self._prepareEmptyExposure(skyInfo) for warpType in warpTypeList} 

374 inputRecorder = {warpType: self.inputRecorder.makeCoaddTempExpRecorder(visitId, len(calExpList)) 

375 for warpType in warpTypeList} 

376 

377 modelPsf = self.config.modelPsf.apply() if self.config.makePsfMatched else None 

378 if dataIdList is None: 

379 dataIdList = ccdIdList 

380 

381 for calExpInd, (calExp, ccdId, dataId) in enumerate(zip(calExpList, ccdIdList, dataIdList)): 

382 self.log.info("Processing calexp %d of %d for this Warp: id=%s", 

383 calExpInd+1, len(calExpList), dataId) 

384 

385 try: 

386 warpedAndMatched = self.warpAndPsfMatch.run(calExp, modelPsf=modelPsf, 

387 wcs=skyInfo.wcs, maxBBox=skyInfo.bbox, 

388 makeDirect=self.config.makeDirect, 

389 makePsfMatched=self.config.makePsfMatched) 

390 except Exception as e: 

391 self.log.warning("WarpAndPsfMatch failed for calexp %s; skipping it: %s", dataId, e) 

392 continue 

393 try: 

394 numGoodPix = {warpType: 0 for warpType in warpTypeList} 

395 for warpType in warpTypeList: 

396 exposure = warpedAndMatched.getDict()[warpType] 

397 if exposure is None: 

398 continue 

399 warp = warps[warpType] 

400 if didSetMetadata[warpType]: 

401 mimg = exposure.getMaskedImage() 

402 mimg *= (warp.getPhotoCalib().getInstFluxAtZeroMagnitude() 

403 / exposure.getPhotoCalib().getInstFluxAtZeroMagnitude()) 

404 del mimg 

405 numGoodPix[warpType] = coaddUtils.copyGoodPixels( 

406 warp.getMaskedImage(), exposure.getMaskedImage(), self.getBadPixelMask()) 

407 totGoodPix[warpType] += numGoodPix[warpType] 

408 self.log.debug("Calexp %s has %d good pixels in this patch (%.1f%%) for %s", 

409 dataId, numGoodPix[warpType], 

410 100.0*numGoodPix[warpType]/skyInfo.bbox.getArea(), warpType) 

411 if numGoodPix[warpType] > 0 and not didSetMetadata[warpType]: 

412 warp.info.id = exposure.info.id 

413 warp.setPhotoCalib(exposure.getPhotoCalib()) 

414 warp.setFilter(exposure.getFilter()) 

415 warp.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo()) 

416 # PSF replaced with CoaddPsf after loop if and only if creating direct warp 

417 warp.setPsf(exposure.getPsf()) 

418 didSetMetadata[warpType] = True 

419 

420 # Need inputRecorder for CoaddApCorrMap for both direct and PSF-matched 

421 inputRecorder[warpType].addCalExp(calExp, ccdId, numGoodPix[warpType]) 

422 

423 except Exception as e: 

424 self.log.warning("Error processing calexp %s; skipping it: %s", dataId, e) 

425 continue 

426 

427 for warpType in warpTypeList: 

428 self.log.info("%sWarp has %d good pixels (%.1f%%)", 

429 warpType, totGoodPix[warpType], 100.0*totGoodPix[warpType]/skyInfo.bbox.getArea()) 

430 

431 if totGoodPix[warpType] > 0 and didSetMetadata[warpType]: 

432 inputRecorder[warpType].finish(warps[warpType], totGoodPix[warpType]) 

433 if warpType == "direct": 

434 warps[warpType].setPsf( 

435 CoaddPsf(inputRecorder[warpType].coaddInputs.ccds, skyInfo.wcs, 

436 self.config.coaddPsf.makeControl())) 

437 else: 

438 if not self.config.doWriteEmptyWarps: 

439 # No good pixels. Exposure still empty 

440 warps[warpType] = None 

441 # NoWorkFound is unnecessary as the downstream tasks will 

442 # adjust the quantum accordingly. 

443 

444 result = pipeBase.Struct(exposures=warps) 

445 return result 

446 

447 def filterInputs(self, indices, inputs): 

448 """Return task inputs with their lists filtered by indices 

449 

450 Parameters 

451 ---------- 

452 indices : `list` of integers 

453 inputs : `dict` of `list` of input connections to be passed to run 

454 """ 

455 for key in inputs.keys(): 

456 # Only down-select on list inputs 

457 if isinstance(inputs[key], list): 

458 inputs[key] = [inputs[key][ind] for ind in indices] 

459 return inputs 

460 

461 def prepareCalibratedExposures(self, calExpList, backgroundList=None, skyCorrList=None, 

462 externalSkyWcsCatalog=None, externalPhotoCalibCatalog=None, 

463 finalizedPsfApCorrCatalog=None, 

464 **kwargs): 

465 """Calibrate and add backgrounds to input calExpList in place 

466 

467 Parameters 

468 ---------- 

469 calExpList : `list` of `lsst.afw.image.Exposure` 

470 Sequence of calexps to be modified in place 

471 backgroundList : `list` of `lsst.afw.math.backgroundList`, optional 

472 Sequence of backgrounds to be added back in if bgSubtracted=False 

473 skyCorrList : `list` of `lsst.afw.math.backgroundList`, optional 

474 Sequence of background corrections to be subtracted if doApplySkyCorr=True 

475 externalSkyWcsCatalog : `lsst.afw.table.ExposureCatalog`, optional 

476 Exposure catalog with external skyWcs to be applied 

477 if config.doApplyExternalSkyWcs=True. Catalog uses the detector id 

478 for the catalog id, sorted on id for fast lookup. 

479 externalPhotoCalibCatalog : `lsst.afw.table.ExposureCatalog`, optional 

480 Exposure catalog with external photoCalib to be applied 

481 if config.doApplyExternalPhotoCalib=True. Catalog uses the detector 

482 id for the catalog id, sorted on id for fast lookup. 

483 finalizedPsfApCorrCatalog : `lsst.afw.table.ExposureCatalog`, optional 

484 Exposure catalog with finalized psf models and aperture correction 

485 maps to be applied if config.doApplyFinalizedPsf=True. Catalog uses 

486 the detector id for the catalog id, sorted on id for fast lookup. 

487 

488 Returns 

489 ------- 

490 indices : `list` [`int`] 

491 Indices of calExpList and friends that have valid photoCalib/skyWcs 

492 """ 

493 backgroundList = len(calExpList)*[None] if backgroundList is None else backgroundList 

494 skyCorrList = len(calExpList)*[None] if skyCorrList is None else skyCorrList 

495 

496 includeCalibVar = self.config.includeCalibVar 

497 

498 indices = [] 

499 for index, (calexp, background, skyCorr) in enumerate(zip(calExpList, 

500 backgroundList, 

501 skyCorrList)): 

502 if not self.config.bgSubtracted: 

503 calexp.maskedImage += background.getImage() 

504 

505 detectorId = calexp.getInfo().getDetector().getId() 

506 

507 # Find the external photoCalib 

508 if externalPhotoCalibCatalog is not None: 

509 row = externalPhotoCalibCatalog.find(detectorId) 

510 if row is None: 

511 self.log.warning("Detector id %s not found in externalPhotoCalibCatalog " 

512 "and will not be used in the warp.", detectorId) 

513 continue 

514 photoCalib = row.getPhotoCalib() 

515 if photoCalib is None: 

516 self.log.warning("Detector id %s has None for photoCalib in externalPhotoCalibCatalog " 

517 "and will not be used in the warp.", detectorId) 

518 continue 

519 calexp.setPhotoCalib(photoCalib) 

520 else: 

521 photoCalib = calexp.getPhotoCalib() 

522 if photoCalib is None: 

523 self.log.warning("Detector id %s has None for photoCalib in the calexp " 

524 "and will not be used in the warp.", detectorId) 

525 continue 

526 

527 # Find and apply external skyWcs 

528 if externalSkyWcsCatalog is not None: 

529 row = externalSkyWcsCatalog.find(detectorId) 

530 if row is None: 

531 self.log.warning("Detector id %s not found in externalSkyWcsCatalog " 

532 "and will not be used in the warp.", detectorId) 

533 continue 

534 skyWcs = row.getWcs() 

535 if skyWcs is None: 

536 self.log.warning("Detector id %s has None for skyWcs in externalSkyWcsCatalog " 

537 "and will not be used in the warp.", detectorId) 

538 continue 

539 calexp.setWcs(skyWcs) 

540 else: 

541 skyWcs = calexp.getWcs() 

542 if skyWcs is None: 

543 self.log.warning("Detector id %s has None for skyWcs in the calexp " 

544 "and will not be used in the warp.", detectorId) 

545 continue 

546 

547 # Find and apply finalized psf and aperture correction 

548 if finalizedPsfApCorrCatalog is not None: 

549 row = finalizedPsfApCorrCatalog.find(detectorId) 

550 if row is None: 

551 self.log.warning("Detector id %s not found in finalizedPsfApCorrCatalog " 

552 "and will not be used in the warp.", detectorId) 

553 continue 

554 psf = row.getPsf() 

555 if psf is None: 

556 self.log.warning("Detector id %s has None for psf in finalizedPsfApCorrCatalog " 

557 "and will not be used in the warp.", detectorId) 

558 continue 

559 calexp.setPsf(psf) 

560 apCorrMap = row.getApCorrMap() 

561 if apCorrMap is None: 

562 self.log.warning("Detector id %s has None for ApCorrMap in finalizedPsfApCorrCatalog " 

563 "and will not be used in the warp.", detectorId) 

564 continue 

565 calexp.info.setApCorrMap(apCorrMap) 

566 

567 # Calibrate the image 

568 calexp.maskedImage = photoCalib.calibrateImage(calexp.maskedImage, 

569 includeScaleUncertainty=includeCalibVar) 

570 calexp.maskedImage /= photoCalib.getCalibrationMean() 

571 # TODO: The images will have a calibration of 1.0 everywhere once RFC-545 is implemented. 

572 # exposure.setCalib(afwImage.Calib(1.0)) 

573 

574 # Apply skycorr 

575 if self.config.doApplySkyCorr: 

576 calexp.maskedImage -= skyCorr.getImage() 

577 

578 indices.append(index) 

579 

580 return indices 

581 

582 @staticmethod 

583 def _prepareEmptyExposure(skyInfo): 

584 """Produce an empty exposure for a given patch""" 

585 exp = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs) 

586 exp.getMaskedImage().set(numpy.nan, afwImage.Mask 

587 .getPlaneBitMask("NO_DATA"), numpy.inf) 

588 return exp 

589 

590 def getWarpTypeList(self): 

591 """Return list of requested warp types per the config. 

592 """ 

593 warpTypeList = [] 

594 if self.config.makeDirect: 

595 warpTypeList.append("direct") 

596 if self.config.makePsfMatched: 

597 warpTypeList.append("psfMatched") 

598 return warpTypeList 

599 

600 

601def reorderRefs(inputRefs, outputSortKeyOrder, dataIdKey): 

602 """Reorder inputRefs per outputSortKeyOrder 

603 

604 Any inputRefs which are lists will be resorted per specified key e.g., 

605 'detector.' Only iterables will be reordered, and values can be of type 

606 `lsst.pipe.base.connections.DeferredDatasetRef` or 

607 `lsst.daf.butler.core.datasets.ref.DatasetRef`. 

608 Returned lists of refs have the same length as the outputSortKeyOrder. 

609 If an outputSortKey not in the inputRef, then it will be padded with None. 

610 If an inputRef contains an inputSortKey that is not in the 

611 outputSortKeyOrder it will be removed. 

612 

613 Parameters 

614 ---------- 

615 inputRefs : `lsst.pipe.base.connections.QuantizedConnection` 

616 Input references to be reordered and padded. 

617 outputSortKeyOrder : iterable 

618 Iterable of values to be compared with inputRef's dataId[dataIdKey] 

619 dataIdKey : `str` 

620 dataIdKey in the dataRefs to compare with the outputSortKeyOrder. 

621 

622 Returns: 

623 -------- 

624 inputRefs: `lsst.pipe.base.connections.QuantizedConnection` 

625 Quantized Connection with sorted DatasetRef values sorted if iterable. 

626 """ 

627 for connectionName, refs in inputRefs: 

628 if isinstance(refs, Iterable): 

629 if hasattr(refs[0], "dataId"): 

630 inputSortKeyOrder = [ref.dataId[dataIdKey] for ref in refs] 

631 else: 

632 inputSortKeyOrder = [ref.datasetRef.dataId[dataIdKey] for ref in refs] 

633 if inputSortKeyOrder != outputSortKeyOrder: 

634 setattr(inputRefs, connectionName, 

635 reorderAndPadList(refs, inputSortKeyOrder, outputSortKeyOrder)) 

636 return inputRefs