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 lsst.pex.exceptions import LengthError 

42 

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

44 

45 

46class CharacterizeImageConnections(pipeBase.PipelineTaskConnections, 

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

48 exposure = cT.Input( 

49 doc="Input exposure data", 

50 name="postISRCCD", 

51 storageClass="Exposure", 

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

53 ) 

54 characterized = cT.Output( 

55 doc="Output characterized data.", 

56 name="icExp", 

57 storageClass="ExposureF", 

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

59 ) 

60 sourceCat = cT.Output( 

61 doc="Output source catalog.", 

62 name="icSrc", 

63 storageClass="SourceCatalog", 

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

65 ) 

66 backgroundModel = cT.Output( 

67 doc="Output background model.", 

68 name="icExpBackground", 

69 storageClass="Background", 

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

71 ) 

72 outputSchema = cT.InitOutput( 

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

74 name="icSrc_schema", 

75 storageClass="SourceCatalog", 

76 ) 

77 

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

79 # Docstring inherited from PipelineTaskConnections 

80 try: 

81 return super().adjustQuantum(datasetRefMap) 

82 except pipeBase.ScalarError as err: 

83 raise pipeBase.ScalarError( 

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

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

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

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

88 ) from err 

89 

90 

91class CharacterizeImageConfig(pipeBase.PipelineTaskConfig, 

92 pipelineConnections=CharacterizeImageConnections): 

93 

94 """!Config for CharacterizeImageTask""" 

95 doMeasurePsf = pexConfig.Field( 

96 dtype=bool, 

97 default=True, 

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

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

100 "config options)" 

101 ) 

102 doWrite = pexConfig.Field( 

103 dtype=bool, 

104 default=True, 

105 doc="Persist results?", 

106 ) 

107 doWriteExposure = pexConfig.Field( 

108 dtype=bool, 

109 default=True, 

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

111 ) 

112 psfIterations = pexConfig.RangeField( 

113 dtype=int, 

114 default=2, 

115 min=1, 

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

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

118 "otherwise more may be wanted.", 

119 ) 

120 background = pexConfig.ConfigurableField( 

121 target=SubtractBackgroundTask, 

122 doc="Configuration for initial background estimation", 

123 ) 

124 detection = pexConfig.ConfigurableField( 

125 target=SourceDetectionTask, 

126 doc="Detect sources" 

127 ) 

128 doDeblend = pexConfig.Field( 

129 dtype=bool, 

130 default=True, 

131 doc="Run deblender input exposure" 

132 ) 

133 deblend = pexConfig.ConfigurableField( 

134 target=SourceDeblendTask, 

135 doc="Split blended source into their components" 

136 ) 

137 measurement = pexConfig.ConfigurableField( 

138 target=SingleFrameMeasurementTask, 

139 doc="Measure sources" 

140 ) 

141 doApCorr = pexConfig.Field( 

142 dtype=bool, 

143 default=True, 

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

145 ) 

146 measureApCorr = pexConfig.ConfigurableField( 

147 target=MeasureApCorrTask, 

148 doc="Subtask to measure aperture corrections" 

149 ) 

150 applyApCorr = pexConfig.ConfigurableField( 

151 target=ApplyApCorrTask, 

152 doc="Subtask to apply aperture corrections" 

153 ) 

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

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

156 catalogCalculation = pexConfig.ConfigurableField( 

157 target=CatalogCalculationTask, 

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

159 ) 

160 useSimplePsf = pexConfig.Field( 

161 dtype=bool, 

162 default=True, 

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

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

165 "converge more robustly and quickly.", 

166 ) 

167 installSimplePsf = pexConfig.ConfigurableField( 

168 target=InstallGaussianPsfTask, 

169 doc="Install a simple PSF model", 

170 ) 

171 refObjLoader = pexConfig.ConfigurableField( 

172 target=LoadIndexedReferenceObjectsTask, 

173 doc="reference object loader", 

174 ) 

175 ref_match = pexConfig.ConfigurableField( 

176 target=RefMatchTask, 

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

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

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

180 ) 

181 measurePsf = pexConfig.ConfigurableField( 

182 target=MeasurePsfTask, 

183 doc="Measure PSF", 

184 ) 

185 repair = pexConfig.ConfigurableField( 

186 target=RepairTask, 

187 doc="Remove cosmic rays", 

188 ) 

189 requireCrForPsf = pexConfig.Field( 

190 dtype=bool, 

191 default=True, 

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

193 ) 

194 checkUnitsParseStrict = pexConfig.Field( 

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

196 dtype=str, 

197 default="raise", 

198 ) 

199 

200 def setDefaults(self): 

201 super().setDefaults() 

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

203 # but these are the values we have been using 

204 self.detection.thresholdValue = 5.0 

205 self.detection.includeThresholdMultiplier = 10.0 

206 self.detection.doTempLocalBackground = False 

207 # do not deblend, as it makes a mess 

208 self.doDeblend = False 

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

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

211 self.doApCorr = True 

212 # minimal set of measurements needed to determine PSF 

213 self.measurement.plugins.names = [ 

214 "base_PixelFlags", 

215 "base_SdssCentroid", 

216 "base_SdssShape", 

217 "base_GaussianFlux", 

218 "base_PsfFlux", 

219 "base_CircularApertureFlux", 

220 ] 

221 

222 def validate(self): 

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

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

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

226 "sources used to measure aperture correction") 

227 

228## \addtogroup LSST_task_documentation 

229## \{ 

230## \page CharacterizeImageTask 

231## \ref CharacterizeImageTask_ "CharacterizeImageTask" 

232## \copybrief CharacterizeImageTask 

233## \} 

234 

235 

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

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

238 

239 @anchor CharacterizeImageTask_ 

240 

241 @section pipe_tasks_characterizeImage_Contents Contents 

242 

243 - @ref pipe_tasks_characterizeImage_Purpose 

244 - @ref pipe_tasks_characterizeImage_Initialize 

245 - @ref pipe_tasks_characterizeImage_IO 

246 - @ref pipe_tasks_characterizeImage_Config 

247 - @ref pipe_tasks_characterizeImage_Debug 

248 

249 

250 @section pipe_tasks_characterizeImage_Purpose Description 

251 

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

253 - detect and measure bright sources 

254 - repair cosmic rays 

255 - measure and subtract background 

256 - measure PSF 

257 

258 @section pipe_tasks_characterizeImage_Initialize Task initialisation 

259 

260 @copydoc \_\_init\_\_ 

261 

262 @section pipe_tasks_characterizeImage_IO Invoking the Task 

263 

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

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

266 

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

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

269 

270 @section pipe_tasks_characterizeImage_Config Configuration parameters 

271 

272 See @ref CharacterizeImageConfig 

273 

274 @section pipe_tasks_characterizeImage_Debug Debug variables 

275 

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

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

278 

279 CharacterizeImageTask has a debug dictionary with the following keys: 

280 <dl> 

281 <dt>frame 

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

283 <dt>repair_iter 

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

285 <dt>background_iter 

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

287 <dt>measure_iter 

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

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

290 <dt>psf 

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

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

293 <dt>repair 

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

295 <dt>measure 

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

297 </dl> 

298 

299 For example, put something like: 

300 @code{.py} 

301 import lsstDebug 

302 def DebugInfo(name): 

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

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

305 di.display = dict( 

306 repair = True, 

307 ) 

308 

309 return di 

310 

311 lsstDebug.Info = DebugInfo 

312 @endcode 

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

314 

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

316 """ 

317 

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

319 

320 ConfigClass = CharacterizeImageConfig 

321 _DefaultName = "characterizeImage" 

322 RunnerClass = pipeBase.ButlerInitializedTaskRunner 

323 

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

325 inputs = butlerQC.get(inputRefs) 

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

327 exposureIdInfo = ExposureIdInfo() 

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

329 returnMaxBits=True) 

330 inputs['exposureIdInfo'] = exposureIdInfo 

331 outputs = self.run(**inputs) 

332 butlerQC.put(outputs, outputRefs) 

333 

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

335 """!Construct a CharacterizeImageTask 

336 

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

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

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

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

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

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

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

344 butler argument. 

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

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

347 """ 

348 super().__init__(**kwargs) 

349 

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

351 schema = SourceTable.makeMinimalSchema() 

352 self.schema = schema 

353 self.makeSubtask("background") 

354 self.makeSubtask("installSimplePsf") 

355 self.makeSubtask("repair") 

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

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

358 if not refObjLoader: 

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

360 refObjLoader = self.refObjLoader 

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

362 self.algMetadata = dafBase.PropertyList() 

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

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

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

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

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

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

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

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

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

372 self._frame = self._initialFrame 

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

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

375 

376 def getInitOutputDatasets(self): 

377 outputCatSchema = afwTable.SourceCatalog(self.schema) 

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

379 return {'outputSchema': outputCatSchema} 

380 

381 @pipeBase.timeMethod 

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

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

384 

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

386 

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

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

389 If None then unpersist from "postISRCCD". 

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

391 - set psf to the measured PSF 

392 - set apCorrMap to the measured aperture correction 

393 - subtract background 

394 - interpolate over cosmic rays 

395 - update detection and cosmic ray mask planes 

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

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

398 which is typical for image characterization. 

399 A refined background model is output. 

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

401 and the exposure and background arguments must be None; 

402 if False the exposure must be provided. 

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

404 

405 @return same data as the characterize method 

406 """ 

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

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

409 

410 if doUnpersist: 

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

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

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

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

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

416 

417 exposureIdInfo = dataRef.get("expIdInfo") 

418 

419 charRes = self.run( 

420 exposure=exposure, 

421 exposureIdInfo=exposureIdInfo, 

422 background=background, 

423 ) 

424 

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

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

427 if self.config.doWriteExposure: 

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

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

430 

431 return charRes 

432 

433 @pipeBase.timeMethod 

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

435 """!Characterize a science image 

436 

437 Peforms the following operations: 

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

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

440 - interpolate over cosmic rays 

441 - perform final measurement 

442 

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

444 The following changes are made: 

445 - update or set psf 

446 - set apCorrMap 

447 - update detection and cosmic ray mask planes 

448 - subtract background and interpolate over cosmic rays 

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

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

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

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

453 which is typical for image characterization. 

454 

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

456 of detectMeasureAndEstimatePsf: 

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

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

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

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

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

462 """ 

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

464 

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

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

467 self.installSimplePsf.run(exposure=exposure) 

468 

469 if exposureIdInfo is None: 

470 exposureIdInfo = ExposureIdInfo() 

471 

472 # subtract an initial estimate of background level 

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

474 

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

476 for i in range(psfIterations): 

477 dmeRes = self.detectMeasureAndEstimatePsf( 

478 exposure=exposure, 

479 exposureIdInfo=exposureIdInfo, 

480 background=background, 

481 ) 

482 

483 psf = dmeRes.exposure.getPsf() 

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

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

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

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

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

489 

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

491 

492 # perform final repair with final PSF 

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

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

495 

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

497 # if wanted 

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

499 exposureId=exposureIdInfo.expId) 

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

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

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

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

504 self.catalogCalculation.run(dmeRes.sourceCat) 

505 

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

507 

508 return pipeBase.Struct( 

509 exposure=dmeRes.exposure, 

510 sourceCat=dmeRes.sourceCat, 

511 background=dmeRes.background, 

512 psfCellSet=dmeRes.psfCellSet, 

513 

514 characterized=dmeRes.exposure, 

515 backgroundModel=dmeRes.background 

516 ) 

517 

518 @pipeBase.timeMethod 

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

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

521 

522 Performs the following operations: 

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

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

525 - interpolate over cosmic rays with keepCRs=True 

526 - estimate background and subtract it from the exposure 

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

528 - if config.doMeasurePsf: 

529 - measure PSF 

530 

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

532 The following changes are made: 

533 - update or set psf 

534 - update detection and cosmic ray mask planes 

535 - subtract background 

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

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

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

539 

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

541 of detect sources, measure sources and estimate PSF: 

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

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

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

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

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

547 """ 

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

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

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

551 self.installSimplePsf.run(exposure=exposure) 

552 

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

554 if self.config.requireCrForPsf: 554 ↛ 557line 554 didn't jump to line 557, because the condition on line 554 was never false

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

556 else: 

557 try: 

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

559 except LengthError: 

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

561 self.config.repair.cosmicray.nCrPixelMax) 

562 

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

564 

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

566 background = BackgroundList() 

567 

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

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

570 table.setMetadata(self.algMetadata) 

571 

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

573 sourceCat = detRes.sources 

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

575 for bg in detRes.fpSets.background: 

576 background.append(bg) 

577 

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

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

580 

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

582 

583 measPsfRes = pipeBase.Struct(cellSet=None) 

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

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

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

587 else: 

588 matches = None 

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

590 expId=exposureIdInfo.expId) 

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

592 

593 return pipeBase.Struct( 

594 exposure=exposure, 

595 sourceCat=sourceCat, 

596 background=background, 

597 psfCellSet=measPsfRes.cellSet, 

598 ) 

599 

600 def getSchemaCatalogs(self): 

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

602 """ 

603 sourceCat = SourceCatalog(self.schema) 

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

605 return {"icSrc": sourceCat} 

606 

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

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

609 

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

611 @param[in] exposure exposure to display 

612 @param[in] sourceCat source catalog to display 

613 """ 

614 val = getDebugFrame(self._display, itemName) 

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

616 return 

617 

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

619 self._frame += 1