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 

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, datasetRefMap: pipeBase.InputQuantizedConnection): 

80 # Docstring inherited from PipelineTaskConnections 

81 try: 

82 return super().adjustQuantum(datasetRefMap) 

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 ) 

166 computeSummaryStats = pexConfig.ConfigurableField( 

167 target=ComputeExposureSummaryStatsTask, 

168 doc="Subtask to run computeSummaryStats on exposure" 

169 ) 

170 useSimplePsf = pexConfig.Field( 

171 dtype=bool, 

172 default=True, 

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

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

175 "converge more robustly and quickly.", 

176 ) 

177 installSimplePsf = pexConfig.ConfigurableField( 

178 target=InstallGaussianPsfTask, 

179 doc="Install a simple PSF model", 

180 ) 

181 refObjLoader = pexConfig.ConfigurableField( 

182 target=LoadIndexedReferenceObjectsTask, 

183 doc="reference object loader", 

184 ) 

185 ref_match = pexConfig.ConfigurableField( 

186 target=RefMatchTask, 

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

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

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

190 ) 

191 measurePsf = pexConfig.ConfigurableField( 

192 target=MeasurePsfTask, 

193 doc="Measure PSF", 

194 ) 

195 repair = pexConfig.ConfigurableField( 

196 target=RepairTask, 

197 doc="Remove cosmic rays", 

198 ) 

199 requireCrForPsf = pexConfig.Field( 

200 dtype=bool, 

201 default=True, 

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

203 ) 

204 checkUnitsParseStrict = pexConfig.Field( 

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

206 dtype=str, 

207 default="raise", 

208 ) 

209 

210 def setDefaults(self): 

211 super().setDefaults() 

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

213 # but these are the values we have been using 

214 self.detection.thresholdValue = 5.0 

215 self.detection.includeThresholdMultiplier = 10.0 

216 self.detection.doTempLocalBackground = False 

217 # do not deblend, as it makes a mess 

218 self.doDeblend = False 

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

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

221 self.doApCorr = True 

222 # minimal set of measurements needed to determine PSF 

223 self.measurement.plugins.names = [ 

224 "base_PixelFlags", 

225 "base_SdssCentroid", 

226 "base_SdssShape", 

227 "base_GaussianFlux", 

228 "base_PsfFlux", 

229 "base_CircularApertureFlux", 

230 ] 

231 

232 def validate(self): 

233 if self.doApCorr and not self.measurePsf: 

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

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

236 "sources used to measure aperture correction") 

237 

238## \addtogroup LSST_task_documentation 

239## \{ 

240## \page CharacterizeImageTask 

241## \ref CharacterizeImageTask_ "CharacterizeImageTask" 

242## \copybrief CharacterizeImageTask 

243## \} 

244 

245 

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

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

248 

249 @anchor CharacterizeImageTask_ 

250 

251 @section pipe_tasks_characterizeImage_Contents Contents 

252 

253 - @ref pipe_tasks_characterizeImage_Purpose 

254 - @ref pipe_tasks_characterizeImage_Initialize 

255 - @ref pipe_tasks_characterizeImage_IO 

256 - @ref pipe_tasks_characterizeImage_Config 

257 - @ref pipe_tasks_characterizeImage_Debug 

258 

259 

260 @section pipe_tasks_characterizeImage_Purpose Description 

261 

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

263 - detect and measure bright sources 

264 - repair cosmic rays 

265 - measure and subtract background 

266 - measure PSF 

267 

268 @section pipe_tasks_characterizeImage_Initialize Task initialisation 

269 

270 @copydoc \_\_init\_\_ 

271 

272 @section pipe_tasks_characterizeImage_IO Invoking the Task 

273 

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

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

276 

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

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

279 

280 @section pipe_tasks_characterizeImage_Config Configuration parameters 

281 

282 See @ref CharacterizeImageConfig 

283 

284 @section pipe_tasks_characterizeImage_Debug Debug variables 

285 

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

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

288 

289 CharacterizeImageTask has a debug dictionary with the following keys: 

290 <dl> 

291 <dt>frame 

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

293 <dt>repair_iter 

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

295 <dt>background_iter 

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

297 <dt>measure_iter 

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

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

300 <dt>psf 

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

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

303 <dt>repair 

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

305 <dt>measure 

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

307 </dl> 

308 

309 For example, put something like: 

310 @code{.py} 

311 import lsstDebug 

312 def DebugInfo(name): 

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

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

315 di.display = dict( 

316 repair = True, 

317 ) 

318 

319 return di 

320 

321 lsstDebug.Info = DebugInfo 

322 @endcode 

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

324 

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

326 """ 

327 

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

329 

330 ConfigClass = CharacterizeImageConfig 

331 _DefaultName = "characterizeImage" 

332 RunnerClass = pipeBase.ButlerInitializedTaskRunner 

333 

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

335 inputs = butlerQC.get(inputRefs) 

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

337 exposureIdInfo = ExposureIdInfo() 

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

339 returnMaxBits=True) 

340 inputs['exposureIdInfo'] = exposureIdInfo 

341 outputs = self.run(**inputs) 

342 butlerQC.put(outputs, outputRefs) 

343 

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

345 """!Construct a CharacterizeImageTask 

346 

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

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

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

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

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

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

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

354 butler argument. 

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

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

357 """ 

358 super().__init__(**kwargs) 

359 

360 if schema is None: 

361 schema = SourceTable.makeMinimalSchema() 

362 self.schema = schema 

363 self.makeSubtask("background") 

364 self.makeSubtask("installSimplePsf") 

365 self.makeSubtask("repair") 

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

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

368 if not refObjLoader: 

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

370 refObjLoader = self.refObjLoader 

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

372 self.algMetadata = dafBase.PropertyList() 

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

374 if self.config.doDeblend: 

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

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

377 if self.config.doApCorr: 

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

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

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

381 if self.config.doComputeSummaryStats: 

382 self.makeSubtask('computeSummaryStats') 

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

384 self._frame = self._initialFrame 

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

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

387 

388 def getInitOutputDatasets(self): 

389 outputCatSchema = afwTable.SourceCatalog(self.schema) 

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

391 return {'outputSchema': outputCatSchema} 

392 

393 @pipeBase.timeMethod 

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

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

396 

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

398 

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

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

401 If None then unpersist from "postISRCCD". 

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

403 - set psf to the measured PSF 

404 - set apCorrMap to the measured aperture correction 

405 - subtract background 

406 - interpolate over cosmic rays 

407 - update detection and cosmic ray mask planes 

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

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

410 which is typical for image characterization. 

411 A refined background model is output. 

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

413 and the exposure and background arguments must be None; 

414 if False the exposure must be provided. 

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

416 

417 @return same data as the characterize method 

418 """ 

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

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

421 

422 if doUnpersist: 

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

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

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

426 elif exposure is None: 

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

428 

429 exposureIdInfo = dataRef.get("expIdInfo") 

430 

431 charRes = self.run( 

432 exposure=exposure, 

433 exposureIdInfo=exposureIdInfo, 

434 background=background, 

435 ) 

436 

437 if self.config.doWrite: 

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

439 if self.config.doWriteExposure: 

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

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

442 

443 return charRes 

444 

445 @pipeBase.timeMethod 

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

447 """!Characterize a science image 

448 

449 Peforms the following operations: 

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

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

452 - interpolate over cosmic rays 

453 - perform final measurement 

454 

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

456 The following changes are made: 

457 - update or set psf 

458 - set apCorrMap 

459 - update detection and cosmic ray mask planes 

460 - subtract background and interpolate over cosmic rays 

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

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

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

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

465 which is typical for image characterization. 

466 

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

468 of detectMeasureAndEstimatePsf: 

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

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

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

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

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

474 """ 

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

476 

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

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

479 self.installSimplePsf.run(exposure=exposure) 

480 

481 if exposureIdInfo is None: 

482 exposureIdInfo = ExposureIdInfo() 

483 

484 # subtract an initial estimate of background level 

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

486 

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

488 for i in range(psfIterations): 

489 dmeRes = self.detectMeasureAndEstimatePsf( 

490 exposure=exposure, 

491 exposureIdInfo=exposureIdInfo, 

492 background=background, 

493 ) 

494 

495 psf = dmeRes.exposure.getPsf() 

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

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

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

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

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

501 

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

503 

504 # perform final repair with final PSF 

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

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

507 

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

509 # if wanted 

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

511 exposureId=exposureIdInfo.expId) 

512 if self.config.doApCorr: 

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

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

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

516 self.catalogCalculation.run(dmeRes.sourceCat) 

517 if self.config.doComputeSummaryStats: 

518 summary = self.computeSummaryStats.run(exposure=dmeRes.exposure, 

519 sources=dmeRes.sourceCat, 

520 background=dmeRes.background) 

521 dmeRes.exposure.getInfo().setSummaryStats(summary) 

522 

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

524 

525 return pipeBase.Struct( 

526 exposure=dmeRes.exposure, 

527 sourceCat=dmeRes.sourceCat, 

528 background=dmeRes.background, 

529 psfCellSet=dmeRes.psfCellSet, 

530 

531 characterized=dmeRes.exposure, 

532 backgroundModel=dmeRes.background 

533 ) 

534 

535 @pipeBase.timeMethod 

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

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

538 

539 Performs the following operations: 

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

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

542 - interpolate over cosmic rays with keepCRs=True 

543 - estimate background and subtract it from the exposure 

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

545 - if config.doMeasurePsf: 

546 - measure PSF 

547 

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

549 The following changes are made: 

550 - update or set psf 

551 - update detection and cosmic ray mask planes 

552 - subtract background 

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

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

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

556 

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

558 of detect sources, measure sources and estimate PSF: 

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

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

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

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

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

564 """ 

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

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

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

568 self.installSimplePsf.run(exposure=exposure) 

569 

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

571 if self.config.requireCrForPsf: 

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

573 else: 

574 try: 

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

576 except LengthError: 

577 self.log.warn("Skipping cosmic ray detection: Too many CR pixels (max %0.f)" % 

578 self.config.repair.cosmicray.nCrPixelMax) 

579 

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

581 

582 if background is None: 

583 background = BackgroundList() 

584 

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

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

587 table.setMetadata(self.algMetadata) 

588 

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

590 sourceCat = detRes.sources 

591 if detRes.fpSets.background: 

592 for bg in detRes.fpSets.background: 

593 background.append(bg) 

594 

595 if self.config.doDeblend: 

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

597 

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

599 

600 measPsfRes = pipeBase.Struct(cellSet=None) 

601 if self.config.doMeasurePsf: 

602 if self.measurePsf.usesMatches: 

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

604 else: 

605 matches = None 

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

607 expId=exposureIdInfo.expId) 

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

609 

610 return pipeBase.Struct( 

611 exposure=exposure, 

612 sourceCat=sourceCat, 

613 background=background, 

614 psfCellSet=measPsfRes.cellSet, 

615 ) 

616 

617 def getSchemaCatalogs(self): 

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

619 """ 

620 sourceCat = SourceCatalog(self.schema) 

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

622 return {"icSrc": sourceCat} 

623 

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

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

626 

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

628 @param[in] exposure exposure to display 

629 @param[in] sourceCat source catalog to display 

630 """ 

631 val = getDebugFrame(self._display, itemName) 

632 if not val: 

633 return 

634 

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

636 self._frame += 1