Hide keyboard shortcuts

Hot-keys 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

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, IdFactory 

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 

41 

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

43 

44 

45class CharacterizeImageConnections(pipeBase.PipelineTaskConnections, 

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

47 exposure = cT.Input( 

48 doc="Input exposure data", 

49 name="postISRCCD", 

50 storageClass="ExposureF", 

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

52 ) 

53 characterized = cT.Output( 

54 doc="Output characterized data.", 

55 name="icExp", 

56 storageClass="ExposureF", 

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

58 ) 

59 sourceCat = cT.Output( 

60 doc="Output source catalog.", 

61 name="icSrc", 

62 storageClass="SourceCatalog", 

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

64 ) 

65 backgroundModel = cT.Output( 

66 doc="Output background model.", 

67 name="icExpBackground", 

68 storageClass="Background", 

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

70 ) 

71 outputSchema = cT.InitOutput( 

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

73 name="icSrc_schema", 

74 storageClass="SourceCatalog", 

75 ) 

76 

77 def adjustQuantum(self, datasetRefMap: pipeBase.InputQuantizedConnection): 

78 # Docstring inherited from PipelineTaskConnections 

79 try: 

80 return super().adjustQuantum(datasetRefMap) 

81 except pipeBase.ScalarError as err: 

82 raise pipeBase.ScalarError( 

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

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

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

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

87 ) from err 

88 

89 

90class CharacterizeImageConfig(pipeBase.PipelineTaskConfig, 

91 pipelineConnections=CharacterizeImageConnections): 

92 

93 """!Config for CharacterizeImageTask""" 

94 doMeasurePsf = pexConfig.Field( 

95 dtype=bool, 

96 default=True, 

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

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

99 "config options)" 

100 ) 

101 doWrite = pexConfig.Field( 

102 dtype=bool, 

103 default=True, 

104 doc="Persist results?", 

105 ) 

106 doWriteExposure = pexConfig.Field( 

107 dtype=bool, 

108 default=True, 

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

110 ) 

111 psfIterations = pexConfig.RangeField( 

112 dtype=int, 

113 default=2, 

114 min=1, 

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

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

117 "otherwise more may be wanted.", 

118 ) 

119 background = pexConfig.ConfigurableField( 

120 target=SubtractBackgroundTask, 

121 doc="Configuration for initial background estimation", 

122 ) 

123 detection = pexConfig.ConfigurableField( 

124 target=SourceDetectionTask, 

125 doc="Detect sources" 

126 ) 

127 doDeblend = pexConfig.Field( 

128 dtype=bool, 

129 default=True, 

130 doc="Run deblender input exposure" 

131 ) 

132 deblend = pexConfig.ConfigurableField( 

133 target=SourceDeblendTask, 

134 doc="Split blended source into their components" 

135 ) 

136 measurement = pexConfig.ConfigurableField( 

137 target=SingleFrameMeasurementTask, 

138 doc="Measure sources" 

139 ) 

140 doApCorr = pexConfig.Field( 

141 dtype=bool, 

142 default=True, 

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

144 ) 

145 measureApCorr = pexConfig.ConfigurableField( 

146 target=MeasureApCorrTask, 

147 doc="Subtask to measure aperture corrections" 

148 ) 

149 applyApCorr = pexConfig.ConfigurableField( 

150 target=ApplyApCorrTask, 

151 doc="Subtask to apply aperture corrections" 

152 ) 

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

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

155 catalogCalculation = pexConfig.ConfigurableField( 

156 target=CatalogCalculationTask, 

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

158 ) 

159 useSimplePsf = pexConfig.Field( 

160 dtype=bool, 

161 default=True, 

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

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

164 "converge more robustly and quickly.", 

165 ) 

166 installSimplePsf = pexConfig.ConfigurableField( 

167 target=InstallGaussianPsfTask, 

168 doc="Install a simple PSF model", 

169 ) 

170 refObjLoader = pexConfig.ConfigurableField( 

171 target=LoadIndexedReferenceObjectsTask, 

172 doc="reference object loader", 

173 ) 

174 ref_match = pexConfig.ConfigurableField( 

175 target=RefMatchTask, 

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

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

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

179 ) 

180 measurePsf = pexConfig.ConfigurableField( 

181 target=MeasurePsfTask, 

182 doc="Measure PSF", 

183 ) 

184 repair = pexConfig.ConfigurableField( 

185 target=RepairTask, 

186 doc="Remove cosmic rays", 

187 ) 

188 checkUnitsParseStrict = pexConfig.Field( 

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

190 dtype=str, 

191 default="raise", 

192 ) 

193 

194 def setDefaults(self): 

195 super().setDefaults() 

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

197 # but these are the values we have been using 

198 self.detection.thresholdValue = 5.0 

199 self.detection.includeThresholdMultiplier = 10.0 

200 self.detection.doTempLocalBackground = False 

201 # do not deblend, as it makes a mess 

202 self.doDeblend = False 

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

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

205 self.doApCorr = True 

206 # minimal set of measurements needed to determine PSF 

207 self.measurement.plugins.names = [ 

208 "base_PixelFlags", 

209 "base_SdssCentroid", 

210 "base_SdssShape", 

211 "base_GaussianFlux", 

212 "base_PsfFlux", 

213 "base_CircularApertureFlux", 

214 ] 

215 

216 def validate(self): 

217 if self.doApCorr and not self.measurePsf: 217 ↛ 218line 217 didn't jump to line 218, because the condition on line 217 was never true

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

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

220 "sources used to measure aperture correction") 

221 

222## \addtogroup LSST_task_documentation 

223## \{ 

224## \page CharacterizeImageTask 

225## \ref CharacterizeImageTask_ "CharacterizeImageTask" 

226## \copybrief CharacterizeImageTask 

227## \} 

228 

229 

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

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

232 

233 @anchor CharacterizeImageTask_ 

234 

235 @section pipe_tasks_characterizeImage_Contents Contents 

236 

237 - @ref pipe_tasks_characterizeImage_Purpose 

238 - @ref pipe_tasks_characterizeImage_Initialize 

239 - @ref pipe_tasks_characterizeImage_IO 

240 - @ref pipe_tasks_characterizeImage_Config 

241 - @ref pipe_tasks_characterizeImage_Debug 

242 

243 

244 @section pipe_tasks_characterizeImage_Purpose Description 

245 

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

247 - detect and measure bright sources 

248 - repair cosmic rays 

249 - measure and subtract background 

250 - measure PSF 

251 

252 @section pipe_tasks_characterizeImage_Initialize Task initialisation 

253 

254 @copydoc \_\_init\_\_ 

255 

256 @section pipe_tasks_characterizeImage_IO Invoking the Task 

257 

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

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

260 

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

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

263 

264 @section pipe_tasks_characterizeImage_Config Configuration parameters 

265 

266 See @ref CharacterizeImageConfig 

267 

268 @section pipe_tasks_characterizeImage_Debug Debug variables 

269 

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

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

272 

273 CharacterizeImageTask has a debug dictionary with the following keys: 

274 <dl> 

275 <dt>frame 

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

277 <dt>repair_iter 

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

279 <dt>background_iter 

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

281 <dt>measure_iter 

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

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

284 <dt>psf 

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

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

287 <dt>repair 

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

289 <dt>measure 

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

291 </dl> 

292 

293 For example, put something like: 

294 @code{.py} 

295 import lsstDebug 

296 def DebugInfo(name): 

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

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

299 di.display = dict( 

300 repair = True, 

301 ) 

302 

303 return di 

304 

305 lsstDebug.Info = DebugInfo 

306 @endcode 

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

308 

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

310 """ 

311 

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

313 

314 ConfigClass = CharacterizeImageConfig 

315 _DefaultName = "characterizeImage" 

316 RunnerClass = pipeBase.ButlerInitializedTaskRunner 

317 

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

319 inputs = butlerQC.get(inputRefs) 

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

321 exposureIdInfo = ExposureIdInfo() 

322 exposureIdInfo.expId, exposureIdInfo.expBits = butlerQC.quantum.dataId.pack("visit_detector", 

323 returnMaxBits=True) 

324 inputs['exposureIdInfo'] = exposureIdInfo 

325 outputs = self.run(**inputs) 

326 butlerQC.put(outputs, outputRefs) 

327 

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

329 """!Construct a CharacterizeImageTask 

330 

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

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

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

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

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

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

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

338 butler argument. 

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

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

341 """ 

342 super().__init__(**kwargs) 

343 

344 if schema is None: 344 ↛ 346line 344 didn't jump to line 346, because the condition on line 344 was never false

345 schema = SourceTable.makeMinimalSchema() 

346 self.schema = schema 

347 self.makeSubtask("background") 

348 self.makeSubtask("installSimplePsf") 

349 self.makeSubtask("repair") 

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

351 if self.config.doMeasurePsf and self.measurePsf.usesMatches: 351 ↛ 352line 351 didn't jump to line 352, because the condition on line 351 was never true

352 if not refObjLoader: 

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

354 refObjLoader = self.refObjLoader 

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

356 self.algMetadata = dafBase.PropertyList() 

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

358 if self.config.doDeblend: 358 ↛ 359line 358 didn't jump to line 359, because the condition on line 358 was never true

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

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

361 if self.config.doApCorr: 361 ↛ 364line 361 didn't jump to line 364, because the condition on line 361 was never false

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

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

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

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

366 self._frame = self._initialFrame 

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

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

369 

370 def getInitOutputDatasets(self): 

371 outputCatSchema = afwTable.SourceCatalog(self.schema) 

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

373 return {'outputSchema': outputCatSchema} 

374 

375 @pipeBase.timeMethod 

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

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

378 

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

380 

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

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

383 If None then unpersist from "postISRCCD". 

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

385 - set psf to the measured PSF 

386 - set apCorrMap to the measured aperture correction 

387 - subtract background 

388 - interpolate over cosmic rays 

389 - update detection and cosmic ray mask planes 

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

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

392 which is typical for image characterization. 

393 A refined background model is output. 

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

395 and the exposure and background arguments must be None; 

396 if False the exposure must be provided. 

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

398 

399 @return same data as the characterize method 

400 """ 

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

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

403 

404 if doUnpersist: 

405 if exposure is not None or background is not None: 405 ↛ 406line 405 didn't jump to line 406, because the condition on line 405 was never true

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

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

408 elif exposure is None: 408 ↛ 409line 408 didn't jump to line 409, because the condition on line 408 was never true

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

410 

411 exposureIdInfo = dataRef.get("expIdInfo") 

412 

413 charRes = self.run( 

414 exposure=exposure, 

415 exposureIdInfo=exposureIdInfo, 

416 background=background, 

417 ) 

418 

419 if self.config.doWrite: 419 ↛ 425line 419 didn't jump to line 425, because the condition on line 419 was never false

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

421 if self.config.doWriteExposure: 

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

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

424 

425 return charRes 

426 

427 @pipeBase.timeMethod 

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

429 """!Characterize a science image 

430 

431 Peforms the following operations: 

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

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

434 - interpolate over cosmic rays 

435 - perform final measurement 

436 

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

438 The following changes are made: 

439 - update or set psf 

440 - set apCorrMap 

441 - update detection and cosmic ray mask planes 

442 - subtract background and interpolate over cosmic rays 

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

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

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

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

447 which is typical for image characterization. 

448 

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

450 of detectMeasureAndEstimatePsf: 

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

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

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

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

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

456 """ 

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

458 

459 if not self.config.doMeasurePsf and not exposure.hasPsf(): 459 ↛ 460line 459 didn't jump to line 460, because the condition on line 459 was never true

460 self.log.warn("Source catalog detected and measured with placeholder or default PSF") 

461 self.installSimplePsf.run(exposure=exposure) 

462 

463 if exposureIdInfo is None: 

464 exposureIdInfo = ExposureIdInfo() 

465 

466 # subtract an initial estimate of background level 

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

468 

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

470 for i in range(psfIterations): 

471 dmeRes = self.detectMeasureAndEstimatePsf( 

472 exposure=exposure, 

473 exposureIdInfo=exposureIdInfo, 

474 background=background, 

475 ) 

476 

477 psf = dmeRes.exposure.getPsf() 

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

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

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

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

482 (i + 1, psfSigma, psfDimensions, medBackground)) 

483 

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

485 

486 # perform final repair with final PSF 

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

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

489 

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

491 # if wanted 

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

493 exposureId=exposureIdInfo.expId) 

494 if self.config.doApCorr: 494 ↛ 498line 494 didn't jump to line 498, because the condition on line 494 was never false

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

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

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

498 self.catalogCalculation.run(dmeRes.sourceCat) 

499 

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

501 

502 return pipeBase.Struct( 

503 exposure=dmeRes.exposure, 

504 sourceCat=dmeRes.sourceCat, 

505 background=dmeRes.background, 

506 psfCellSet=dmeRes.psfCellSet, 

507 

508 characterized=dmeRes.exposure, 

509 backgroundModel=dmeRes.background 

510 ) 

511 

512 @pipeBase.timeMethod 

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

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

515 

516 Performs the following operations: 

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

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

519 - interpolate over cosmic rays with keepCRs=True 

520 - estimate background and subtract it from the exposure 

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

522 - if config.doMeasurePsf: 

523 - measure PSF 

524 

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

526 The following changes are made: 

527 - update or set psf 

528 - update detection and cosmic ray mask planes 

529 - subtract background 

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

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

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

533 

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

535 of detect sources, measure sources and estimate PSF: 

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

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

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

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

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

541 """ 

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

543 if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf): 543 ↛ 548line 543 didn't jump to line 548, because the condition on line 543 was never false

544 self.log.warn("Source catalog detected and measured with placeholder or default PSF") 

545 self.installSimplePsf.run(exposure=exposure) 

546 

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

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

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

550 

551 if background is None: 551 ↛ 552line 551 didn't jump to line 552, because the condition on line 551 was never true

552 background = BackgroundList() 

553 

554 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) 

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

556 table.setMetadata(self.algMetadata) 

557 

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

559 sourceCat = detRes.sources 

560 if detRes.fpSets.background: 560 ↛ 564line 560 didn't jump to line 564, because the condition on line 560 was never false

561 for bg in detRes.fpSets.background: 

562 background.append(bg) 

563 

564 if self.config.doDeblend: 564 ↛ 565line 564 didn't jump to line 565, because the condition on line 564 was never true

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

566 

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

568 

569 measPsfRes = pipeBase.Struct(cellSet=None) 

570 if self.config.doMeasurePsf: 570 ↛ 577line 570 didn't jump to line 577, because the condition on line 570 was never false

571 if self.measurePsf.usesMatches: 571 ↛ 572line 571 didn't jump to line 572, because the condition on line 571 was never true

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

573 else: 

574 matches = None 

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

576 expId=exposureIdInfo.expId) 

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

578 

579 return pipeBase.Struct( 

580 exposure=exposure, 

581 sourceCat=sourceCat, 

582 background=background, 

583 psfCellSet=measPsfRes.cellSet, 

584 ) 

585 

586 def getSchemaCatalogs(self): 

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

588 """ 

589 sourceCat = SourceCatalog(self.schema) 

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

591 return {"icSrc": sourceCat} 

592 

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

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

595 

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

597 @param[in] exposure exposure to display 

598 @param[in] sourceCat source catalog to display 

599 """ 

600 val = getDebugFrame(self._display, itemName) 

601 if not val: 601 ↛ 604line 601 didn't jump to line 604, because the condition on line 601 was never false

602 return 

603 

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

605 self._frame += 1