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

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

201 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 

43from lsst.utils.timer import timeMethod 

44 

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

46 

47 

48class CharacterizeImageConnections(pipeBase.PipelineTaskConnections, 

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

50 exposure = cT.Input( 

51 doc="Input exposure data", 

52 name="postISRCCD", 

53 storageClass="Exposure", 

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

55 ) 

56 characterized = cT.Output( 

57 doc="Output characterized data.", 

58 name="icExp", 

59 storageClass="ExposureF", 

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

61 ) 

62 sourceCat = cT.Output( 

63 doc="Output source catalog.", 

64 name="icSrc", 

65 storageClass="SourceCatalog", 

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

67 ) 

68 backgroundModel = cT.Output( 

69 doc="Output background model.", 

70 name="icExpBackground", 

71 storageClass="Background", 

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

73 ) 

74 outputSchema = cT.InitOutput( 

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

76 name="icSrc_schema", 

77 storageClass="SourceCatalog", 

78 ) 

79 

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

81 # Docstring inherited from PipelineTaskConnections 

82 try: 

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

84 except pipeBase.ScalarError as err: 

85 raise pipeBase.ScalarError( 

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

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

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

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

90 ) from err 

91 

92 

93class CharacterizeImageConfig(pipeBase.PipelineTaskConfig, 

94 pipelineConnections=CharacterizeImageConnections): 

95 

96 """!Config for CharacterizeImageTask""" 

97 doMeasurePsf = pexConfig.Field( 

98 dtype=bool, 

99 default=True, 

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

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

102 "config options)" 

103 ) 

104 doWrite = pexConfig.Field( 

105 dtype=bool, 

106 default=True, 

107 doc="Persist results?", 

108 ) 

109 doWriteExposure = pexConfig.Field( 

110 dtype=bool, 

111 default=True, 

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

113 ) 

114 psfIterations = pexConfig.RangeField( 

115 dtype=int, 

116 default=2, 

117 min=1, 

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

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

120 "otherwise more may be wanted.", 

121 ) 

122 background = pexConfig.ConfigurableField( 

123 target=SubtractBackgroundTask, 

124 doc="Configuration for initial background estimation", 

125 ) 

126 detection = pexConfig.ConfigurableField( 

127 target=SourceDetectionTask, 

128 doc="Detect sources" 

129 ) 

130 doDeblend = pexConfig.Field( 

131 dtype=bool, 

132 default=True, 

133 doc="Run deblender input exposure" 

134 ) 

135 deblend = pexConfig.ConfigurableField( 

136 target=SourceDeblendTask, 

137 doc="Split blended source into their components" 

138 ) 

139 measurement = pexConfig.ConfigurableField( 

140 target=SingleFrameMeasurementTask, 

141 doc="Measure sources" 

142 ) 

143 doApCorr = pexConfig.Field( 

144 dtype=bool, 

145 default=True, 

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

147 ) 

148 measureApCorr = pexConfig.ConfigurableField( 

149 target=MeasureApCorrTask, 

150 doc="Subtask to measure aperture corrections" 

151 ) 

152 applyApCorr = pexConfig.ConfigurableField( 

153 target=ApplyApCorrTask, 

154 doc="Subtask to apply aperture corrections" 

155 ) 

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

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

158 catalogCalculation = pexConfig.ConfigurableField( 

159 target=CatalogCalculationTask, 

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

161 ) 

162 doComputeSummaryStats = pexConfig.Field( 

163 dtype=bool, 

164 default=True, 

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

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

167 "with DM-30701.") 

168 ) 

169 computeSummaryStats = pexConfig.ConfigurableField( 

170 target=ComputeExposureSummaryStatsTask, 

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

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

173 "with DM-30701.") 

174 ) 

175 useSimplePsf = pexConfig.Field( 

176 dtype=bool, 

177 default=True, 

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

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

180 "converge more robustly and quickly.", 

181 ) 

182 installSimplePsf = pexConfig.ConfigurableField( 

183 target=InstallGaussianPsfTask, 

184 doc="Install a simple PSF model", 

185 ) 

186 refObjLoader = pexConfig.ConfigurableField( 

187 target=LoadIndexedReferenceObjectsTask, 

188 doc="reference object loader", 

189 ) 

190 ref_match = pexConfig.ConfigurableField( 

191 target=RefMatchTask, 

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

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

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

195 ) 

196 measurePsf = pexConfig.ConfigurableField( 

197 target=MeasurePsfTask, 

198 doc="Measure PSF", 

199 ) 

200 repair = pexConfig.ConfigurableField( 

201 target=RepairTask, 

202 doc="Remove cosmic rays", 

203 ) 

204 requireCrForPsf = pexConfig.Field( 

205 dtype=bool, 

206 default=True, 

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

208 ) 

209 checkUnitsParseStrict = pexConfig.Field( 

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

211 dtype=str, 

212 default="raise", 

213 ) 

214 

215 def setDefaults(self): 

216 super().setDefaults() 

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

218 # but these are the values we have been using 

219 self.detection.thresholdValue = 5.0 

220 self.detection.includeThresholdMultiplier = 10.0 

221 self.detection.doTempLocalBackground = False 

222 # do not deblend, as it makes a mess 

223 self.doDeblend = False 

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

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

226 self.doApCorr = True 

227 # minimal set of measurements needed to determine PSF 

228 self.measurement.plugins.names = [ 

229 "base_PixelFlags", 

230 "base_SdssCentroid", 

231 "base_SdssShape", 

232 "base_GaussianFlux", 

233 "base_PsfFlux", 

234 "base_CircularApertureFlux", 

235 ] 

236 

237 def validate(self): 

238 if self.doApCorr and not self.measurePsf: 

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

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

241 "sources used to measure aperture correction") 

242 

243## \addtogroup LSST_task_documentation 

244## \{ 

245## \page page_CharacterizeImageTask CharacterizeImageTask 

246## \ref CharacterizeImageTask_ "CharacterizeImageTask" 

247## \copybrief CharacterizeImageTask 

248## \} 

249 

250 

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

252 r"""! 

253 Measure bright sources and use this to estimate background and PSF of an exposure 

254 

255 @anchor CharacterizeImageTask_ 

256 

257 @section pipe_tasks_characterizeImage_Contents Contents 

258 

259 - @ref pipe_tasks_characterizeImage_Purpose 

260 - @ref pipe_tasks_characterizeImage_Initialize 

261 - @ref pipe_tasks_characterizeImage_IO 

262 - @ref pipe_tasks_characterizeImage_Config 

263 - @ref pipe_tasks_characterizeImage_Debug 

264 

265 @section pipe_tasks_characterizeImage_Purpose Description 

266 

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

268 - detect and measure bright sources 

269 - repair cosmic rays 

270 - measure and subtract background 

271 - measure PSF 

272 

273 @section pipe_tasks_characterizeImage_Initialize Task initialisation 

274 

275 @copydoc \_\_init\_\_ 

276 

277 @section pipe_tasks_characterizeImage_IO Invoking the Task 

278 

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

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

281 

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

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

284 

285 @section pipe_tasks_characterizeImage_Config Configuration parameters 

286 

287 See @ref CharacterizeImageConfig 

288 

289 @section pipe_tasks_characterizeImage_Debug Debug variables 

290 

291 The command line task interface supports a flag 

292 `--debug` to import `debug.py` from your `$PYTHONPATH`; see 

293 <a href="https://pipelines.lsst.io/modules/lsstDebug/">the lsstDebug documentation</a> 

294 for more about `debug.py`. 

295 

296 CharacterizeImageTask has a debug dictionary with the following keys: 

297 <dl> 

298 <dt>frame 

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

300 <dt>repair_iter 

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

302 <dt>background_iter 

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

304 <dt>measure_iter 

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

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

307 <dt>psf 

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

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

310 <dt>repair 

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

312 <dt>measure 

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

314 </dl> 

315 

316 For example, put something like: 

317 @code{.py} 

318 import lsstDebug 

319 def DebugInfo(name): 

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

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

322 di.display = dict( 

323 repair = True, 

324 ) 

325 

326 return di 

327 

328 lsstDebug.Info = DebugInfo 

329 @endcode 

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

331 

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

333 """ 

334 

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

336 

337 ConfigClass = CharacterizeImageConfig 

338 _DefaultName = "characterizeImage" 

339 RunnerClass = pipeBase.ButlerInitializedTaskRunner 

340 

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

342 inputs = butlerQC.get(inputRefs) 

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

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

345 outputs = self.run(**inputs) 

346 butlerQC.put(outputs, outputRefs) 

347 

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

349 """!Construct a CharacterizeImageTask 

350 

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

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

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

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

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

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

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

358 butler argument. 

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

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

361 """ 

362 super().__init__(**kwargs) 

363 

364 if schema is None: 

365 schema = SourceTable.makeMinimalSchema() 

366 self.schema = schema 

367 self.makeSubtask("background") 

368 self.makeSubtask("installSimplePsf") 

369 self.makeSubtask("repair") 

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

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

372 if not refObjLoader: 

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

374 refObjLoader = self.refObjLoader 

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

376 self.algMetadata = dafBase.PropertyList() 

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

378 if self.config.doDeblend: 

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

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

381 if self.config.doApCorr: 

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

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

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

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

386 self._frame = self._initialFrame 

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

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

389 

390 def getInitOutputDatasets(self): 

391 outputCatSchema = afwTable.SourceCatalog(self.schema) 

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

393 return {'outputSchema': outputCatSchema} 

394 

395 @timeMethod 

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

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

398 

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

400 

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

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

403 If None then unpersist from "postISRCCD". 

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

405 - set psf to the measured PSF 

406 - set apCorrMap to the measured aperture correction 

407 - subtract background 

408 - interpolate over cosmic rays 

409 - update detection and cosmic ray mask planes 

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

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

412 which is typical for image characterization. 

413 A refined background model is output. 

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

415 and the exposure and background arguments must be None; 

416 if False the exposure must be provided. 

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

418 

419 @return same data as the characterize method 

420 """ 

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

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

423 

424 if doUnpersist: 

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

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

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

428 elif exposure is None: 

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

430 

431 exposureIdInfo = dataRef.get("expIdInfo") 

432 

433 charRes = self.run( 

434 exposure=exposure, 

435 exposureIdInfo=exposureIdInfo, 

436 background=background, 

437 ) 

438 

439 if self.config.doWrite: 

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

441 if self.config.doWriteExposure: 

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

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

444 

445 return charRes 

446 

447 @timeMethod 

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

449 """!Characterize a science image 

450 

451 Peforms the following operations: 

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

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

454 - interpolate over cosmic rays 

455 - perform final measurement 

456 

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

458 The following changes are made: 

459 - update or set psf 

460 - set apCorrMap 

461 - update detection and cosmic ray mask planes 

462 - subtract background and interpolate over cosmic rays 

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

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

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

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

467 which is typical for image characterization. 

468 

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

470 of detectMeasureAndEstimatePsf: 

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

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

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

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

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

476 """ 

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

478 

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

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

481 self.installSimplePsf.run(exposure=exposure) 

482 

483 if exposureIdInfo is None: 

484 exposureIdInfo = ExposureIdInfo() 

485 

486 # subtract an initial estimate of background level 

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

488 

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

490 for i in range(psfIterations): 

491 dmeRes = self.detectMeasureAndEstimatePsf( 

492 exposure=exposure, 

493 exposureIdInfo=exposureIdInfo, 

494 background=background, 

495 ) 

496 

497 psf = dmeRes.exposure.getPsf() 

498 # Just need a rough estimate; average positions are fine 

499 psfAvgPos = psf.getAveragePosition() 

500 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius() 

501 psfDimensions = psf.computeImage(psfAvgPos).getDimensions() 

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

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

504 i + 1, psfSigma, psfDimensions, medBackground) 

505 if np.isnan(psfSigma): 

506 raise RuntimeError("PSF sigma is NaN, cannot continue PSF determination.") 

507 

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

509 

510 # perform final repair with final PSF 

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

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

513 

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

515 # if wanted 

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

517 exposureId=exposureIdInfo.expId) 

518 if self.config.doApCorr: 

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

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

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

522 self.catalogCalculation.run(dmeRes.sourceCat) 

523 

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

525 

526 return pipeBase.Struct( 

527 exposure=dmeRes.exposure, 

528 sourceCat=dmeRes.sourceCat, 

529 background=dmeRes.background, 

530 psfCellSet=dmeRes.psfCellSet, 

531 

532 characterized=dmeRes.exposure, 

533 backgroundModel=dmeRes.background 

534 ) 

535 

536 @timeMethod 

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

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

539 

540 Performs the following operations: 

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

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

543 - interpolate over cosmic rays with keepCRs=True 

544 - estimate background and subtract it from the exposure 

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

546 - if config.doMeasurePsf: 

547 - measure PSF 

548 

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

550 The following changes are made: 

551 - update or set psf 

552 - update detection and cosmic ray mask planes 

553 - subtract background 

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

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

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

557 

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

559 of detect sources, measure sources and estimate PSF: 

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

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

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

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

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

565 """ 

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

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

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

569 self.installSimplePsf.run(exposure=exposure) 

570 

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

572 if self.config.requireCrForPsf: 

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

574 else: 

575 try: 

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

577 except LengthError: 

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

579 self.config.repair.cosmicray.nCrPixelMax) 

580 

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

582 

583 if background is None: 

584 background = BackgroundList() 

585 

586 sourceIdFactory = exposureIdInfo.makeSourceIdFactory() 

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

588 table.setMetadata(self.algMetadata) 

589 

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

591 sourceCat = detRes.sources 

592 if detRes.fpSets.background: 

593 for bg in detRes.fpSets.background: 

594 background.append(bg) 

595 

596 if self.config.doDeblend: 

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

598 

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

600 

601 measPsfRes = pipeBase.Struct(cellSet=None) 

602 if self.config.doMeasurePsf: 

603 if self.measurePsf.usesMatches: 

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

605 else: 

606 matches = None 

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

608 expId=exposureIdInfo.expId) 

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

610 

611 return pipeBase.Struct( 

612 exposure=exposure, 

613 sourceCat=sourceCat, 

614 background=background, 

615 psfCellSet=measPsfRes.cellSet, 

616 ) 

617 

618 def getSchemaCatalogs(self): 

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

620 """ 

621 sourceCat = SourceCatalog(self.schema) 

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

623 return {"icSrc": sourceCat} 

624 

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

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

627 

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

629 @param[in] exposure exposure to display 

630 @param[in] sourceCat source catalog to display 

631 """ 

632 val = getDebugFrame(self._display, itemName) 

633 if not val: 

634 return 

635 

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

637 self._frame += 1