Coverage for python/lsst/pipe/tasks/characterizeImage.py: 30%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

197 statements  

1# 

2# LSST Data Management System 

3# Copyright 2008-2015 AURA/LSST. 

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 <https://www.lsstcorp.org/LegalNotices/>. 

21# 

22import numpy as np 

23 

24from lsstDebug import getDebugFrame 

25import lsst.afw.table as afwTable 

26import lsst.pex.config as pexConfig 

27import lsst.pipe.base as pipeBase 

28import lsst.daf.base as dafBase 

29import lsst.pipe.base.connectionTypes as cT 

30from lsst.afw.math import BackgroundList 

31from lsst.afw.table import SourceTable, SourceCatalog 

32from lsst.meas.algorithms import SubtractBackgroundTask, SourceDetectionTask, MeasureApCorrTask 

33from lsst.meas.algorithms.installGaussianPsf import InstallGaussianPsfTask 

34from lsst.meas.astrom import RefMatchTask, displayAstrometry 

35from lsst.meas.algorithms import LoadIndexedReferenceObjectsTask 

36from lsst.obs.base import ExposureIdInfo 

37from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask 

38from lsst.meas.deblender import SourceDeblendTask 

39from .measurePsf import MeasurePsfTask 

40from .repair import RepairTask 

41from .computeExposureSummaryStats import ComputeExposureSummaryStatsTask 

42from lsst.pex.exceptions import LengthError 

43 

44__all__ = ["CharacterizeImageConfig", "CharacterizeImageTask"] 

45 

46 

47class CharacterizeImageConnections(pipeBase.PipelineTaskConnections, 

48 dimensions=("instrument", "visit", "detector")): 

49 exposure = cT.Input( 

50 doc="Input exposure data", 

51 name="postISRCCD", 

52 storageClass="Exposure", 

53 dimensions=["instrument", "exposure", "detector"], 

54 ) 

55 characterized = cT.Output( 

56 doc="Output characterized data.", 

57 name="icExp", 

58 storageClass="ExposureF", 

59 dimensions=["instrument", "visit", "detector"], 

60 ) 

61 sourceCat = cT.Output( 

62 doc="Output source catalog.", 

63 name="icSrc", 

64 storageClass="SourceCatalog", 

65 dimensions=["instrument", "visit", "detector"], 

66 ) 

67 backgroundModel = cT.Output( 

68 doc="Output background model.", 

69 name="icExpBackground", 

70 storageClass="Background", 

71 dimensions=["instrument", "visit", "detector"], 

72 ) 

73 outputSchema = cT.InitOutput( 

74 doc="Schema of the catalog produced by CharacterizeImage", 

75 name="icSrc_schema", 

76 storageClass="SourceCatalog", 

77 ) 

78 

79 def adjustQuantum(self, inputs, outputs, label, dataId): 

80 # Docstring inherited from PipelineTaskConnections 

81 try: 

82 return super().adjustQuantum(inputs, outputs, label, dataId) 

83 except pipeBase.ScalarError as err: 

84 raise pipeBase.ScalarError( 

85 "CharacterizeImageTask can at present only be run on visits that are associated with " 

86 "exactly one exposure. Either this is not a valid exposure for this pipeline, or the " 

87 "snap-combination step you probably want hasn't been configured to run between ISR and " 

88 "this task (as of this writing, that would be because it hasn't been implemented yet)." 

89 ) from err 

90 

91 

92class CharacterizeImageConfig(pipeBase.PipelineTaskConfig, 

93 pipelineConnections=CharacterizeImageConnections): 

94 

95 """!Config for CharacterizeImageTask""" 

96 doMeasurePsf = pexConfig.Field( 

97 dtype=bool, 

98 default=True, 

99 doc="Measure PSF? If False then for all subsequent operations use either existing PSF " 

100 "model when present, or install simple PSF model when not (see installSimplePsf " 

101 "config options)" 

102 ) 

103 doWrite = pexConfig.Field( 

104 dtype=bool, 

105 default=True, 

106 doc="Persist results?", 

107 ) 

108 doWriteExposure = pexConfig.Field( 

109 dtype=bool, 

110 default=True, 

111 doc="Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.", 

112 ) 

113 psfIterations = pexConfig.RangeField( 

114 dtype=int, 

115 default=2, 

116 min=1, 

117 doc="Number of iterations of detect sources, measure sources, " 

118 "estimate PSF. If useSimplePsf is True then 2 should be plenty; " 

119 "otherwise more may be wanted.", 

120 ) 

121 background = pexConfig.ConfigurableField( 

122 target=SubtractBackgroundTask, 

123 doc="Configuration for initial background estimation", 

124 ) 

125 detection = pexConfig.ConfigurableField( 

126 target=SourceDetectionTask, 

127 doc="Detect sources" 

128 ) 

129 doDeblend = pexConfig.Field( 

130 dtype=bool, 

131 default=True, 

132 doc="Run deblender input exposure" 

133 ) 

134 deblend = pexConfig.ConfigurableField( 

135 target=SourceDeblendTask, 

136 doc="Split blended source into their components" 

137 ) 

138 measurement = pexConfig.ConfigurableField( 

139 target=SingleFrameMeasurementTask, 

140 doc="Measure sources" 

141 ) 

142 doApCorr = pexConfig.Field( 

143 dtype=bool, 

144 default=True, 

145 doc="Run subtasks to measure and apply aperture corrections" 

146 ) 

147 measureApCorr = pexConfig.ConfigurableField( 

148 target=MeasureApCorrTask, 

149 doc="Subtask to measure aperture corrections" 

150 ) 

151 applyApCorr = pexConfig.ConfigurableField( 

152 target=ApplyApCorrTask, 

153 doc="Subtask to apply aperture corrections" 

154 ) 

155 # If doApCorr is False, and the exposure does not have apcorrections already applied, the 

156 # active plugins in catalogCalculation almost certainly should not contain the characterization plugin 

157 catalogCalculation = pexConfig.ConfigurableField( 

158 target=CatalogCalculationTask, 

159 doc="Subtask to run catalogCalculation plugins on catalog" 

160 ) 

161 doComputeSummaryStats = pexConfig.Field( 

162 dtype=bool, 

163 default=True, 

164 doc="Run subtask to measure exposure summary statistics", 

165 deprecated=("This subtask has been moved to CalibrateTask " 

166 "with DM-30701.") 

167 ) 

168 computeSummaryStats = pexConfig.ConfigurableField( 

169 target=ComputeExposureSummaryStatsTask, 

170 doc="Subtask to run computeSummaryStats on exposure", 

171 deprecated=("This subtask has been moved to CalibrateTask " 

172 "with DM-30701.") 

173 ) 

174 useSimplePsf = pexConfig.Field( 

175 dtype=bool, 

176 default=True, 

177 doc="Replace the existing PSF model with a simplified version that has the same sigma " 

178 "at the start of each PSF determination iteration? Doing so makes PSF determination " 

179 "converge more robustly and quickly.", 

180 ) 

181 installSimplePsf = pexConfig.ConfigurableField( 

182 target=InstallGaussianPsfTask, 

183 doc="Install a simple PSF model", 

184 ) 

185 refObjLoader = pexConfig.ConfigurableField( 

186 target=LoadIndexedReferenceObjectsTask, 

187 doc="reference object loader", 

188 ) 

189 ref_match = pexConfig.ConfigurableField( 

190 target=RefMatchTask, 

191 doc="Task to load and match reference objects. Only used if measurePsf can use matches. " 

192 "Warning: matching will only work well if the initial WCS is accurate enough " 

193 "to give good matches (roughly: good to 3 arcsec across the CCD).", 

194 ) 

195 measurePsf = pexConfig.ConfigurableField( 

196 target=MeasurePsfTask, 

197 doc="Measure PSF", 

198 ) 

199 repair = pexConfig.ConfigurableField( 

200 target=RepairTask, 

201 doc="Remove cosmic rays", 

202 ) 

203 requireCrForPsf = pexConfig.Field( 

204 dtype=bool, 

205 default=True, 

206 doc="Require cosmic ray detection and masking to run successfully before measuring the PSF." 

207 ) 

208 checkUnitsParseStrict = pexConfig.Field( 

209 doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'", 

210 dtype=str, 

211 default="raise", 

212 ) 

213 

214 def setDefaults(self): 

215 super().setDefaults() 

216 # just detect bright stars; includeThresholdMultipler=10 seems large, 

217 # but these are the values we have been using 

218 self.detection.thresholdValue = 5.0 

219 self.detection.includeThresholdMultiplier = 10.0 

220 self.detection.doTempLocalBackground = False 

221 # do not deblend, as it makes a mess 

222 self.doDeblend = False 

223 # measure and apply aperture correction; note: measuring and applying aperture 

224 # correction are disabled until the final measurement, after PSF is measured 

225 self.doApCorr = True 

226 # minimal set of measurements needed to determine PSF 

227 self.measurement.plugins.names = [ 

228 "base_PixelFlags", 

229 "base_SdssCentroid", 

230 "base_SdssShape", 

231 "base_GaussianFlux", 

232 "base_PsfFlux", 

233 "base_CircularApertureFlux", 

234 ] 

235 

236 def validate(self): 

237 if self.doApCorr and not self.measurePsf: 

238 raise RuntimeError("Must measure PSF to measure aperture correction, " 

239 "because flags determined by PSF measurement are used to identify " 

240 "sources used to measure aperture correction") 

241 

242## \addtogroup LSST_task_documentation 

243## \{ 

244## \page CharacterizeImageTask 

245## \ref CharacterizeImageTask_ "CharacterizeImageTask" 

246## \copybrief CharacterizeImageTask 

247## \} 

248 

249 

250class CharacterizeImageTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

251 r"""!Measure bright sources and use this to estimate background and PSF of an exposure 

252 

253 @anchor CharacterizeImageTask_ 

254 

255 @section pipe_tasks_characterizeImage_Contents Contents 

256 

257 - @ref pipe_tasks_characterizeImage_Purpose 

258 - @ref pipe_tasks_characterizeImage_Initialize 

259 - @ref pipe_tasks_characterizeImage_IO 

260 - @ref pipe_tasks_characterizeImage_Config 

261 - @ref pipe_tasks_characterizeImage_Debug 

262 

263 

264 @section pipe_tasks_characterizeImage_Purpose Description 

265 

266 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask): 

267 - detect and measure bright sources 

268 - repair cosmic rays 

269 - measure and subtract background 

270 - measure PSF 

271 

272 @section pipe_tasks_characterizeImage_Initialize Task initialisation 

273 

274 @copydoc \_\_init\_\_ 

275 

276 @section pipe_tasks_characterizeImage_IO Invoking the Task 

277 

278 If you want this task to unpersist inputs or persist outputs, then call 

279 the `runDataRef` method (a thin wrapper around the `run` method). 

280 

281 If you already have the inputs unpersisted and do not want to persist the output 

282 then it is more direct to call the `run` method: 

283 

284 @section pipe_tasks_characterizeImage_Config Configuration parameters 

285 

286 See @ref CharacterizeImageConfig 

287 

288 @section pipe_tasks_characterizeImage_Debug Debug variables 

289 

290 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag 

291 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`. 

292 

293 CharacterizeImageTask has a debug dictionary with the following keys: 

294 <dl> 

295 <dt>frame 

296 <dd>int: if specified, the frame of first debug image displayed (defaults to 1) 

297 <dt>repair_iter 

298 <dd>bool; if True display image after each repair in the measure PSF loop 

299 <dt>background_iter 

300 <dd>bool; if True display image after each background subtraction in the measure PSF loop 

301 <dt>measure_iter 

302 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop 

303 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols. 

304 <dt>psf 

305 <dd>bool; if True display image and sources after PSF is measured; 

306 this will be identical to the final image displayed by measure_iter if measure_iter is true 

307 <dt>repair 

308 <dd>bool; if True display image and sources after final repair 

309 <dt>measure 

310 <dd>bool; if True display image and sources after final measurement 

311 </dl> 

312 

313 For example, put something like: 

314 @code{.py} 

315 import lsstDebug 

316 def DebugInfo(name): 

317 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 

318 if name == "lsst.pipe.tasks.characterizeImage": 

319 di.display = dict( 

320 repair = True, 

321 ) 

322 

323 return di 

324 

325 lsstDebug.Info = DebugInfo 

326 @endcode 

327 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag. 

328 

329 Some subtasks may have their own debug variables; see individual Task documentation. 

330 """ 

331 

332 # Example description used to live here, removed 2-20-2017 by MSSG 

333 

334 ConfigClass = CharacterizeImageConfig 

335 _DefaultName = "characterizeImage" 

336 RunnerClass = pipeBase.ButlerInitializedTaskRunner 

337 

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

339 inputs = butlerQC.get(inputRefs) 

340 if 'exposureIdInfo' not in inputs.keys(): 

341 inputs['exposureIdInfo'] = ExposureIdInfo.fromDataId(butlerQC.quantum.dataId, "visit_detector") 

342 outputs = self.run(**inputs) 

343 butlerQC.put(outputs, outputRefs) 

344 

345 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs): 

346 """!Construct a CharacterizeImageTask 

347 

348 @param[in] butler A butler object is passed to the refObjLoader constructor in case 

349 it is needed to load catalogs. May be None if a catalog-based star selector is 

350 not used, if the reference object loader constructor does not require a butler, 

351 or if a reference object loader is passed directly via the refObjLoader argument. 

352 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an 

353 external reference catalog to a catalog-based star selector. May be None if a 

354 catalog star selector is not used or the loader can be constructed from the 

355 butler argument. 

356 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None 

357 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask 

358 """ 

359 super().__init__(**kwargs) 

360 

361 if schema is None: 

362 schema = SourceTable.makeMinimalSchema() 

363 self.schema = schema 

364 self.makeSubtask("background") 

365 self.makeSubtask("installSimplePsf") 

366 self.makeSubtask("repair") 

367 self.makeSubtask("measurePsf", schema=self.schema) 

368 if self.config.doMeasurePsf and self.measurePsf.usesMatches: 

369 if not refObjLoader: 

370 self.makeSubtask('refObjLoader', butler=butler) 

371 refObjLoader = self.refObjLoader 

372 self.makeSubtask("ref_match", refObjLoader=refObjLoader) 

373 self.algMetadata = dafBase.PropertyList() 

374 self.makeSubtask('detection', schema=self.schema) 

375 if self.config.doDeblend: 

376 self.makeSubtask("deblend", schema=self.schema) 

377 self.makeSubtask('measurement', schema=self.schema, algMetadata=self.algMetadata) 

378 if self.config.doApCorr: 

379 self.makeSubtask('measureApCorr', schema=self.schema) 

380 self.makeSubtask('applyApCorr', schema=self.schema) 

381 self.makeSubtask('catalogCalculation', schema=self.schema) 

382 self._initialFrame = getDebugFrame(self._display, "frame") or 1 

383 self._frame = self._initialFrame 

384 self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict) 

385 self.outputSchema = afwTable.SourceCatalog(self.schema) 

386 

387 def getInitOutputDatasets(self): 

388 outputCatSchema = afwTable.SourceCatalog(self.schema) 

389 outputCatSchema.getTable().setMetadata(self.algMetadata) 

390 return {'outputSchema': outputCatSchema} 

391 

392 @pipeBase.timeMethod 

393 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True): 

394 """!Characterize a science image and, if wanted, persist the results 

395 

396 This simply unpacks the exposure and passes it to the characterize method to do the work. 

397 

398 @param[in] dataRef: butler data reference for science exposure 

399 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 

400 If None then unpersist from "postISRCCD". 

401 The following changes are made, depending on the config: 

402 - set psf to the measured PSF 

403 - set apCorrMap to the measured aperture correction 

404 - subtract background 

405 - interpolate over cosmic rays 

406 - update detection and cosmic ray mask planes 

407 @param[in,out] background initial model of background already subtracted from exposure 

408 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 

409 which is typical for image characterization. 

410 A refined background model is output. 

411 @param[in] doUnpersist if True the exposure is read from the repository 

412 and the exposure and background arguments must be None; 

413 if False the exposure must be provided. 

414 True is intended for running as a command-line task, False for running as a subtask 

415 

416 @return same data as the characterize method 

417 """ 

418 self._frame = self._initialFrame # reset debug display frame 

419 self.log.info("Processing %s", dataRef.dataId) 

420 

421 if doUnpersist: 

422 if exposure is not None or background is not None: 

423 raise RuntimeError("doUnpersist true; exposure and background must be None") 

424 exposure = dataRef.get("postISRCCD", immediate=True) 

425 elif exposure is None: 

426 raise RuntimeError("doUnpersist false; exposure must be provided") 

427 

428 exposureIdInfo = dataRef.get("expIdInfo") 

429 

430 charRes = self.run( 

431 exposure=exposure, 

432 exposureIdInfo=exposureIdInfo, 

433 background=background, 

434 ) 

435 

436 if self.config.doWrite: 

437 dataRef.put(charRes.sourceCat, "icSrc") 

438 if self.config.doWriteExposure: 

439 dataRef.put(charRes.exposure, "icExp") 

440 dataRef.put(charRes.background, "icExpBackground") 

441 

442 return charRes 

443 

444 @pipeBase.timeMethod 

445 def run(self, exposure, exposureIdInfo=None, background=None): 

446 """!Characterize a science image 

447 

448 Peforms the following operations: 

449 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false: 

450 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details) 

451 - interpolate over cosmic rays 

452 - perform final measurement 

453 

454 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 

455 The following changes are made: 

456 - update or set psf 

457 - set apCorrMap 

458 - update detection and cosmic ray mask planes 

459 - subtract background and interpolate over cosmic rays 

460 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo). 

461 If not provided, returned SourceCatalog IDs will not be globally unique. 

462 @param[in,out] background initial model of background already subtracted from exposure 

463 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 

464 which is typical for image characterization. 

465 

466 @return pipe_base Struct containing these fields, all from the final iteration 

467 of detectMeasureAndEstimatePsf: 

468 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays, 

469 mask is updated accordingly, and the PSF model is set 

470 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog) 

471 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 

472 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 

473 """ 

474 self._frame = self._initialFrame # reset debug display frame 

475 

476 if not self.config.doMeasurePsf and not exposure.hasPsf(): 

477 self.log.info("CharacterizeImageTask initialized with 'simple' PSF.") 

478 self.installSimplePsf.run(exposure=exposure) 

479 

480 if exposureIdInfo is None: 

481 exposureIdInfo = ExposureIdInfo() 

482 

483 # subtract an initial estimate of background level 

484 background = self.background.run(exposure).background 

485 

486 psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1 

487 for i in range(psfIterations): 

488 dmeRes = self.detectMeasureAndEstimatePsf( 

489 exposure=exposure, 

490 exposureIdInfo=exposureIdInfo, 

491 background=background, 

492 ) 

493 

494 psf = dmeRes.exposure.getPsf() 

495 psfSigma = psf.computeShape().getDeterminantRadius() 

496 psfDimensions = psf.computeImage().getDimensions() 

497 medBackground = np.median(dmeRes.background.getImage().getArray()) 

498 self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f", 

499 i + 1, psfSigma, psfDimensions, medBackground) 

500 

501 self.display("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) 

502 

503 # perform final repair with final PSF 

504 self.repair.run(exposure=dmeRes.exposure) 

505 self.display("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) 

506 

507 # perform final measurement with final PSF, including measuring and applying aperture correction, 

508 # if wanted 

509 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure, 

510 exposureId=exposureIdInfo.expId) 

511 if self.config.doApCorr: 

512 apCorrMap = self.measureApCorr.run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap 

513 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap) 

514 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap()) 

515 self.catalogCalculation.run(dmeRes.sourceCat) 

516 

517 self.display("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) 

518 

519 return pipeBase.Struct( 

520 exposure=dmeRes.exposure, 

521 sourceCat=dmeRes.sourceCat, 

522 background=dmeRes.background, 

523 psfCellSet=dmeRes.psfCellSet, 

524 

525 characterized=dmeRes.exposure, 

526 backgroundModel=dmeRes.background 

527 ) 

528 

529 @pipeBase.timeMethod 

530 def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background): 

531 """!Perform one iteration of detect, measure and estimate PSF 

532 

533 Performs the following operations: 

534 - if config.doMeasurePsf or not exposure.hasPsf(): 

535 - install a simple PSF model (replacing the existing one, if need be) 

536 - interpolate over cosmic rays with keepCRs=True 

537 - estimate background and subtract it from the exposure 

538 - detect, deblend and measure sources, and subtract a refined background model; 

539 - if config.doMeasurePsf: 

540 - measure PSF 

541 

542 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar) 

543 The following changes are made: 

544 - update or set psf 

545 - update detection and cosmic ray mask planes 

546 - subtract background 

547 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo) 

548 @param[in,out] background initial model of background already subtracted from exposure 

549 (an lsst.afw.math.BackgroundList). 

550 

551 @return pipe_base Struct containing these fields, all from the final iteration 

552 of detect sources, measure sources and estimate PSF: 

553 - exposure characterized exposure; image is repaired by interpolating over cosmic rays, 

554 mask is updated accordingly, and the PSF model is set 

555 - sourceCat detected sources (an lsst.afw.table.SourceCatalog) 

556 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 

557 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 

558 """ 

559 # install a simple PSF model, if needed or wanted 

560 if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf): 

561 self.log.info("PSF estimation initialized with 'simple' PSF") 

562 self.installSimplePsf.run(exposure=exposure) 

563 

564 # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model) 

565 if self.config.requireCrForPsf: 

566 self.repair.run(exposure=exposure, keepCRs=True) 

567 else: 

568 try: 

569 self.repair.run(exposure=exposure, keepCRs=True) 

570 except LengthError: 

571 self.log.warning("Skipping cosmic ray detection: Too many CR pixels (max %0.f)", 

572 self.config.repair.cosmicray.nCrPixelMax) 

573 

574 self.display("repair_iter", exposure=exposure) 

575 

576 if background is None: 

577 background = BackgroundList() 

578 

579 sourceIdFactory = exposureIdInfo.makeSourceIdFactory() 

580 table = SourceTable.make(self.schema, sourceIdFactory) 

581 table.setMetadata(self.algMetadata) 

582 

583 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) 

584 sourceCat = detRes.sources 

585 if detRes.fpSets.background: 

586 for bg in detRes.fpSets.background: 

587 background.append(bg) 

588 

589 if self.config.doDeblend: 

590 self.deblend.run(exposure=exposure, sources=sourceCat) 

591 

592 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId) 

593 

594 measPsfRes = pipeBase.Struct(cellSet=None) 

595 if self.config.doMeasurePsf: 

596 if self.measurePsf.usesMatches: 

597 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches 

598 else: 

599 matches = None 

600 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches, 

601 expId=exposureIdInfo.expId) 

602 self.display("measure_iter", exposure=exposure, sourceCat=sourceCat) 

603 

604 return pipeBase.Struct( 

605 exposure=exposure, 

606 sourceCat=sourceCat, 

607 background=background, 

608 psfCellSet=measPsfRes.cellSet, 

609 ) 

610 

611 def getSchemaCatalogs(self): 

612 """Return a dict of empty catalogs for each catalog dataset produced by this task. 

613 """ 

614 sourceCat = SourceCatalog(self.schema) 

615 sourceCat.getTable().setMetadata(self.algMetadata) 

616 return {"icSrc": sourceCat} 

617 

618 def display(self, itemName, exposure, sourceCat=None): 

619 """Display exposure and sources on next frame, if display of itemName has been requested 

620 

621 @param[in] itemName name of item in debugInfo 

622 @param[in] exposure exposure to display 

623 @param[in] sourceCat source catalog to display 

624 """ 

625 val = getDebugFrame(self._display, itemName) 

626 if not val: 

627 return 

628 

629 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self._frame, pause=False) 

630 self._frame += 1