Coverage for python/lsst/pipe/tasks/calibrateImage.py: 34%

178 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-27 11:13 +0000

1# This file is part of pipe_tasks. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import lsst.afw.table as afwTable 

23import lsst.afw.image as afwImage 

24import lsst.meas.algorithms 

25import lsst.meas.algorithms.installGaussianPsf 

26import lsst.meas.algorithms.measureApCorr 

27from lsst.meas.algorithms import sourceSelector 

28import lsst.meas.astrom 

29import lsst.meas.deblender 

30import lsst.meas.extensions.shapeHSM 

31import lsst.pex.config as pexConfig 

32import lsst.pipe.base as pipeBase 

33from lsst.pipe.base import connectionTypes 

34from lsst.utils.timer import timeMethod 

35 

36from . import measurePsf, repair, setPrimaryFlags, photoCal, computeExposureSummaryStats, maskStreaks 

37 

38 

39class CalibrateImageConnections(pipeBase.PipelineTaskConnections, 

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

41 

42 astrometry_ref_cat = connectionTypes.PrerequisiteInput( 

43 doc="Reference catalog to use for astrometric calibration.", 

44 name="gaia_dr3_20230707", 

45 storageClass="SimpleCatalog", 

46 dimensions=("skypix",), 

47 deferLoad=True, 

48 multiple=True, 

49 ) 

50 photometry_ref_cat = connectionTypes.PrerequisiteInput( 

51 doc="Reference catalog to use for photometric calibration.", 

52 name="ps1_pv3_3pi_20170110", 

53 storageClass="SimpleCatalog", 

54 dimensions=("skypix",), 

55 deferLoad=True, 

56 multiple=True 

57 ) 

58 

59 exposure = connectionTypes.Input( 

60 doc="Exposure to be calibrated, and detected and measured on.", 

61 name="postISRCCD", 

62 storageClass="Exposure", 

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

64 ) 

65 

66 # outputs 

67 initial_stars_schema = connectionTypes.InitOutput( 

68 doc="Schema of the output initial stars catalog.", 

69 name="initial_stars_schema", 

70 storageClass="SourceCatalog", 

71 ) 

72 

73 # TODO: We want some kind of flag on Exposures/Catalogs to make it obvious 

74 # which components had failed to be computed/persisted 

75 output_exposure = connectionTypes.Output( 

76 doc="Photometrically calibrated exposure with fitted calibrations and summary statistics.", 

77 name="initial_pvi", 

78 storageClass="ExposureF", 

79 dimensions=("instrument", "visit", "detector"), 

80 ) 

81 # TODO DM-40061: persist a parquet version of this! 

82 stars = connectionTypes.Output( 

83 doc="Catalog of unresolved sources detected on the calibrated exposure; " 

84 "includes source footprints.", 

85 name="initial_stars_footprints_detector", 

86 storageClass="SourceCatalog", 

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

88 ) 

89 applied_photo_calib = connectionTypes.Output( 

90 doc="Photometric calibration that was applied to exposure.", 

91 name="initial_photoCalib_detector", 

92 storageClass="PhotoCalib", 

93 dimensions=("instrument", "visit", "detector"), 

94 ) 

95 background = connectionTypes.Output( 

96 doc="Background models estimated during calibration task.", 

97 name="initial_pvi_background", 

98 storageClass="Background", 

99 dimensions=("instrument", "visit", "detector"), 

100 ) 

101 

102 # Optional outputs 

103 

104 # TODO: We need to decide on what intermediate outputs we want to save, 

105 # and which to save by default. 

106 # TODO DM-40061: persist a parquet version of this! 

107 psf_stars = connectionTypes.Output( 

108 doc="Catalog of bright unresolved sources detected on the exposure used for PSF determination; " 

109 "includes source footprints.", 

110 name="initial_psf_stars_footprints", 

111 storageClass="SourceCatalog", 

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

113 ) 

114 astrometry_matches = connectionTypes.Output( 

115 doc="Source to reference catalog matches from the astrometry solver.", 

116 name="initial_astrometry_match_detector", 

117 storageClass="Catalog", 

118 dimensions=("instrument", "visit", "detector"), 

119 ) 

120 photometry_matches = connectionTypes.Output( 

121 doc="Source to reference catalog matches from the photometry solver.", 

122 name="initial_photometry_match_detector", 

123 storageClass="Catalog", 

124 dimensions=("instrument", "visit", "detector"), 

125 ) 

126 

127 def __init__(self, *, config=None): 

128 super().__init__(config=config) 

129 if not config.optional_outputs: 

130 self.outputs.remove("psf_stars") 

131 self.outputs.remove("astrometry_matches") 

132 self.outputs.remove("photometry_matches") 

133 

134 

135class CalibrateImageConfig(pipeBase.PipelineTaskConfig, pipelineConnections=CalibrateImageConnections): 

136 optional_outputs = pexConfig.ListField( 

137 doc="Which optional outputs to save (as their connection name)?", 

138 dtype=str, 

139 # TODO: note somewhere to disable this for benchmarking, but should 

140 # we always have it on for production runs? 

141 default=["psf_stars", "astrometry_matches", "photometry_matches"], 

142 optional=True 

143 ) 

144 

145 # subtasks used during psf characterization 

146 install_simple_psf = pexConfig.ConfigurableField( 

147 target=lsst.meas.algorithms.installGaussianPsf.InstallGaussianPsfTask, 

148 doc="Task to install a simple PSF model into the input exposure to use " 

149 "when detecting bright sources for PSF estimation.", 

150 ) 

151 psf_repair = pexConfig.ConfigurableField( 

152 target=repair.RepairTask, 

153 doc="Task to repair cosmic rays on the exposure before PSF determination.", 

154 ) 

155 psf_subtract_background = pexConfig.ConfigurableField( 

156 target=lsst.meas.algorithms.SubtractBackgroundTask, 

157 doc="Task to perform intial background subtraction, before first detection pass.", 

158 ) 

159 psf_detection = pexConfig.ConfigurableField( 

160 target=lsst.meas.algorithms.SourceDetectionTask, 

161 doc="Task to detect sources for PSF determination." 

162 ) 

163 psf_source_measurement = pexConfig.ConfigurableField( 

164 target=lsst.meas.base.SingleFrameMeasurementTask, 

165 doc="Task to measure sources to be used for psf estimation." 

166 ) 

167 psf_measure_psf = pexConfig.ConfigurableField( 

168 target=measurePsf.MeasurePsfTask, 

169 doc="Task to measure the psf on bright sources." 

170 ) 

171 

172 # TODO DM-39203: we can remove aperture correction from this task once we are 

173 # using the shape-based star/galaxy code. 

174 measure_aperture_correction = pexConfig.ConfigurableField( 

175 target=lsst.meas.algorithms.measureApCorr.MeasureApCorrTask, 

176 doc="Task to compute the aperture correction from the bright stars." 

177 ) 

178 

179 # subtasks used during star measurement 

180 star_detection = pexConfig.ConfigurableField( 

181 target=lsst.meas.algorithms.SourceDetectionTask, 

182 doc="Task to detect stars to return in the output catalog." 

183 ) 

184 star_mask_streaks = pexConfig.ConfigurableField( 

185 target=maskStreaks.MaskStreaksTask, 

186 doc="Task for masking streaks. Adds a STREAK mask plane to an exposure.", 

187 ) 

188 star_deblend = pexConfig.ConfigurableField( 

189 target=lsst.meas.deblender.SourceDeblendTask, 

190 doc="Split blended sources into their components" 

191 ) 

192 star_measurement = pexConfig.ConfigurableField( 

193 target=lsst.meas.base.SingleFrameMeasurementTask, 

194 doc="Task to measure stars to return in the output catalog." 

195 ) 

196 star_apply_aperture_correction = pexConfig.ConfigurableField( 

197 target=lsst.meas.base.ApplyApCorrTask, 

198 doc="Task to apply aperture corrections to the selected stars." 

199 ) 

200 star_catalog_calculation = pexConfig.ConfigurableField( 

201 target=lsst.meas.base.CatalogCalculationTask, 

202 doc="Task to compute extendedness values on the star catalog, " 

203 "for the star selector to remove extended sources." 

204 ) 

205 star_set_primary_flags = pexConfig.ConfigurableField( 

206 target=setPrimaryFlags.SetPrimaryFlagsTask, 

207 doc="Task to add isPrimary to the catalog." 

208 ) 

209 star_selector = lsst.meas.algorithms.sourceSelectorRegistry.makeField( 

210 default="science", 

211 doc="Task to select isolated stars to use for calibration." 

212 ) 

213 

214 # final calibrations and statistics 

215 astrometry = pexConfig.ConfigurableField( 

216 target=lsst.meas.astrom.AstrometryTask, 

217 doc="Task to perform astrometric calibration to fit a WCS.", 

218 ) 

219 astrometry_ref_loader = pexConfig.ConfigField( 

220 dtype=lsst.meas.algorithms.LoadReferenceObjectsConfig, 

221 doc="Configuration of reference object loader for astrometric fit.", 

222 ) 

223 photometry = pexConfig.ConfigurableField( 

224 target=photoCal.PhotoCalTask, 

225 doc="Task to perform photometric calibration to fit a PhotoCalib.", 

226 ) 

227 photometry_ref_loader = pexConfig.ConfigField( 

228 dtype=lsst.meas.algorithms.LoadReferenceObjectsConfig, 

229 doc="Configuration of reference object loader for photometric fit.", 

230 ) 

231 

232 compute_summary_stats = pexConfig.ConfigurableField( 

233 target=computeExposureSummaryStats.ComputeExposureSummaryStatsTask, 

234 doc="Task to to compute summary statistics on the calibrated exposure." 

235 ) 

236 

237 def setDefaults(self): 

238 super().setDefaults() 

239 

240 # Use a very broad PSF here, to throughly reject CRs. 

241 # TODO investigation: a large initial psf guess may make stars look 

242 # like CRs for very good seeing images. 

243 self.install_simple_psf.fwhm = 4 

244 

245 # Only use high S/N sources for PSF determination. 

246 self.psf_detection.thresholdValue = 50.0 

247 # TODO investigation: Probably want False here, but that may require 

248 # tweaking the background spatial scale, to make it small enough to 

249 # prevent extra peaks in the wings of bright objects. 

250 self.psf_detection.doTempLocalBackground = False 

251 # NOTE: we do want reEstimateBackground=True in psf_detection, so that 

252 # each measurement step is done with the best background available. 

253 

254 # Minimal measurement plugins for PSF determination. 

255 # TODO DM-39203: We can drop GaussianFlux and PsfFlux, if we use 

256 # shapeHSM/moments for star/galaxy separation. 

257 # TODO DM-39203: we can remove aperture correction from this task once 

258 # we are using the shape-based star/galaxy code. 

259 self.psf_source_measurement.plugins = ["base_PixelFlags", 

260 "base_SdssCentroid", 

261 "ext_shapeHSM_HsmSourceMoments", 

262 "base_CircularApertureFlux", 

263 "base_GaussianFlux", 

264 "base_PsfFlux", 

265 ] 

266 self.psf_source_measurement.slots.shape = "ext_shapeHSM_HsmSourceMoments" 

267 # Only measure apertures we need for PSF measurement. 

268 # TODO DM-40064: psfex has a hard-coded value of 9 in a psfex-config 

269 # file: make that configurable and/or change it to 12 to be consistent 

270 # with our other uses? 

271 # https://github.com/lsst/meas_extensions_psfex/blob/main/config/default-lsst.psfex#L14 

272 self.psf_source_measurement.plugins["base_CircularApertureFlux"].radii = [9.0, 12.0] 

273 

274 # No extendeness information available: we need the aperture 

275 # corrections to determine that. 

276 self.measure_aperture_correction.sourceSelector["science"].doUnresolved = False 

277 self.measure_aperture_correction.sourceSelector["science"].flags.good = ["calib_psf_used"] 

278 self.measure_aperture_correction.sourceSelector["science"].flags.bad = [] 

279 

280 # TODO investigation: how faint do we have to detect, to be able to 

281 # deblend, etc? We may need star_selector to have a separate value, 

282 # and do initial detection at S/N>5.0? 

283 # Detection for good S/N for astrometry/photometry and other 

284 # downstream tasks. 

285 self.star_detection.thresholdValue = 10.0 

286 self.star_measurement.plugins = ["base_PixelFlags", 

287 "base_SdssCentroid", 

288 "ext_shapeHSM_HsmSourceMoments", 

289 'ext_shapeHSM_HsmPsfMoments', 

290 "base_GaussianFlux", 

291 "base_PsfFlux", 

292 "base_CircularApertureFlux", 

293 ] 

294 self.star_measurement.slots.psfShape = "ext_shapeHSM_HsmPsfMoments" 

295 self.star_measurement.slots.shape = "ext_shapeHSM_HsmSourceMoments" 

296 # Only measure the apertures we need for star selection. 

297 self.star_measurement.plugins["base_CircularApertureFlux"].radii = [12.0] 

298 

299 # Keep track of which footprints contain streaks 

300 self.star_measurement.plugins['base_PixelFlags'].masksFpAnywhere = ['STREAK'] 

301 self.star_measurement.plugins['base_PixelFlags'].masksFpCenter = ['STREAK'] 

302 

303 # Select isolated stars with reliable measurements and no bad flags. 

304 self.star_selector["science"].doFlags = True 

305 self.star_selector["science"].doUnresolved = True 

306 self.star_selector["science"].doSignalToNoise = True 

307 self.star_selector["science"].doIsolated = True 

308 self.star_selector["science"].signalToNoise.minimum = 10.0 

309 

310 # Use the affine WCS fitter (assumes we have a good camera geometry). 

311 self.astrometry.wcsFitter.retarget(lsst.meas.astrom.FitAffineWcsTask) 

312 # phot_g_mean is the primary Gaia band for all input bands. 

313 self.astrometry_ref_loader.anyFilterMapsToThis = "phot_g_mean" 

314 

315 # Do not subselect during fitting; we already selected good stars. 

316 self.astrometry.sourceSelector = "null" 

317 self.photometry.match.sourceSelection.retarget(sourceSelector.NullSourceSelectorTask) 

318 

319 # All sources should be good for PSF summary statistics. 

320 self.compute_summary_stats.starSelection = "calib_photometry_used" 

321 

322 

323class CalibrateImageTask(pipeBase.PipelineTask): 

324 """Compute the PSF, aperture corrections, astrometric and photometric 

325 calibrations, and summary statistics for a single science exposure, and 

326 produce a catalog of brighter stars that were used to calibrate it. 

327 

328 Parameters 

329 ---------- 

330 initial_stars_schema : `lsst.afw.table.Schema` 

331 Schema of the initial_stars output catalog. 

332 """ 

333 _DefaultName = "calibrateImage" 

334 ConfigClass = CalibrateImageConfig 

335 

336 def __init__(self, initial_stars_schema=None, **kwargs): 

337 super().__init__(**kwargs) 

338 

339 # PSF determination subtasks 

340 self.makeSubtask("install_simple_psf") 

341 self.makeSubtask("psf_repair") 

342 self.makeSubtask("psf_subtract_background") 

343 self.psf_schema = afwTable.SourceTable.makeMinimalSchema() 

344 self.makeSubtask("psf_detection", schema=self.psf_schema) 

345 self.makeSubtask("psf_source_measurement", schema=self.psf_schema) 

346 self.makeSubtask("psf_measure_psf", schema=self.psf_schema) 

347 

348 self.makeSubtask("measure_aperture_correction", schema=self.psf_schema) 

349 

350 # star measurement subtasks 

351 if initial_stars_schema is None: 

352 initial_stars_schema = afwTable.SourceTable.makeMinimalSchema() 

353 afwTable.CoordKey.addErrorFields(initial_stars_schema) 

354 self.makeSubtask("star_detection", schema=initial_stars_schema) 

355 self.makeSubtask("star_mask_streaks") 

356 self.makeSubtask("star_deblend", schema=initial_stars_schema) 

357 self.makeSubtask("star_measurement", schema=initial_stars_schema) 

358 self.makeSubtask("star_apply_aperture_correction", schema=initial_stars_schema) 

359 self.makeSubtask("star_catalog_calculation", schema=initial_stars_schema) 

360 self.makeSubtask("star_set_primary_flags", schema=initial_stars_schema, isSingleFrame=True) 

361 self.makeSubtask("star_selector") 

362 

363 self.makeSubtask("astrometry", schema=initial_stars_schema) 

364 self.makeSubtask("photometry", schema=initial_stars_schema) 

365 

366 self.makeSubtask("compute_summary_stats") 

367 

368 # For the butler to persist it. 

369 self.initial_stars_schema = afwTable.SourceCatalog(initial_stars_schema) 

370 

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

372 inputs = butlerQC.get(inputRefs) 

373 

374 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader( 

375 dataIds=[ref.datasetRef.dataId for ref in inputRefs.astrometry_ref_cat], 

376 refCats=inputs.pop("astrometry_ref_cat"), 

377 name=self.config.connections.astrometry_ref_cat, 

378 config=self.config.astrometry_ref_loader, log=self.log) 

379 self.astrometry.setRefObjLoader(astrometry_loader) 

380 

381 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader( 

382 dataIds=[ref.datasetRef.dataId for ref in inputRefs.photometry_ref_cat], 

383 refCats=inputs.pop("photometry_ref_cat"), 

384 name=self.config.connections.photometry_ref_cat, 

385 config=self.config.photometry_ref_loader, log=self.log) 

386 self.photometry.match.setRefObjLoader(photometry_loader) 

387 

388 outputs = self.run(**inputs) 

389 

390 butlerQC.put(outputs, outputRefs) 

391 

392 @timeMethod 

393 def run(self, *, exposure): 

394 """Find stars and perform psf measurement, then do a deeper detection 

395 and measurement and calibrate astrometry and photometry from that. 

396 

397 Parameters 

398 ---------- 

399 exposure : `lsst.afw.image.Exposure` 

400 Post-ISR exposure, with an initial WCS, VisitInfo, and Filter. 

401 Modified in-place during processing. 

402 

403 Returns 

404 ------- 

405 result : `lsst.pipe.base.Struct` 

406 Results as a struct with attributes: 

407 

408 ``output_exposure`` 

409 Calibrated exposure, with pixels in nJy units. 

410 (`lsst.afw.image.Exposure`) 

411 ``stars`` 

412 Stars that were used to calibrate the exposure, with 

413 calibrated fluxes and magnitudes. 

414 (`lsst.afw.table.SourceCatalog`) 

415 ``psf_stars`` 

416 Stars that were used to determine the image PSF. 

417 (`lsst.afw.table.SourceCatalog`) 

418 ``background`` 

419 Background that was fit to the exposure when detecting 

420 ``stars``. (`lsst.afw.math.BackgroundList`) 

421 ``applied_photo_calib`` 

422 Photometric calibration that was fit to the star catalog and 

423 applied to the exposure. (`lsst.afw.image.PhotoCalib`) 

424 ``astrometry_matches`` 

425 Reference catalog stars matches used in the astrometric fit. 

426 (`list` [`lsst.afw.table.ReferenceMatch`] or `lsst.afw.table.BaseCatalog`) 

427 ``photometry_matches`` 

428 Reference catalog stars matches used in the photometric fit. 

429 (`list` [`lsst.afw.table.ReferenceMatch`] or `lsst.afw.table.BaseCatalog`) 

430 """ 

431 psf_stars, background, candidates = self._compute_psf(exposure) 

432 

433 self._measure_aperture_correction(exposure, psf_stars) 

434 

435 stars = self._find_stars(exposure, background) 

436 

437 astrometry_matches, astrometry_meta = self._fit_astrometry(exposure, stars) 

438 stars, photometry_matches, photometry_meta, photo_calib = self._fit_photometry(exposure, stars) 

439 

440 self._summarize(exposure, stars, background) 

441 

442 if self.config.optional_outputs: 

443 astrometry_matches = lsst.meas.astrom.denormalizeMatches(astrometry_matches, astrometry_meta) 

444 photometry_matches = lsst.meas.astrom.denormalizeMatches(photometry_matches, photometry_meta) 

445 

446 return pipeBase.Struct(output_exposure=exposure, 

447 stars=stars, 

448 psf_stars=psf_stars, 

449 background=background, 

450 applied_photo_calib=photo_calib, 

451 astrometry_matches=astrometry_matches, 

452 photometry_matches=photometry_matches) 

453 

454 def _compute_psf(self, exposure, guess_psf=True): 

455 """Find bright sources detected on an exposure and fit a PSF model to 

456 them, repairing likely cosmic rays before detection. 

457 

458 Repair, detect, measure, and compute PSF twice, to ensure the PSF 

459 model does not include contributions from cosmic rays. 

460 

461 Parameters 

462 ---------- 

463 exposure : `lsst.afw.image.Exposure` 

464 Exposure to detect and measure bright stars on. 

465 

466 Returns 

467 ------- 

468 sources : `lsst.afw.table.SourceCatalog` 

469 Catalog of detected bright sources. 

470 background : `lsst.afw.math.BackgroundList` 

471 Background that was fit to the exposure during detection. 

472 cell_set : `lsst.afw.math.SpatialCellSet` 

473 PSF candidates returned by the psf determiner. 

474 """ 

475 self.log.info("First pass detection with Guassian PSF FWHM=%s", self.config.install_simple_psf.fwhm) 

476 self.install_simple_psf.run(exposure=exposure) 

477 

478 background = self.psf_subtract_background.run(exposure=exposure).background 

479 self.psf_repair.run(exposure=exposure, keepCRs=True) 

480 

481 table = afwTable.SourceTable.make(self.psf_schema) 

482 # Re-estimate the background during this detection step, so that 

483 # measurement uses the most accurate background-subtraction. 

484 detections = self.psf_detection.run(table=table, exposure=exposure, background=background) 

485 self.psf_source_measurement.run(detections.sources, exposure) 

486 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources) 

487 # Replace the initial PSF with something simpler for the second 

488 # repair/detect/measure/measure_psf step: this can help it converge. 

489 self.install_simple_psf.run(exposure=exposure) 

490 

491 self.log.info("Re-running repair, detection, and PSF measurement using new simple PSF.") 

492 # TODO investigation: Should we only re-run repair here, to use the 

493 # new PSF? Maybe we *do* need to re-run measurement with PsfFlux, to 

494 # use the fitted PSF? 

495 # TODO investigation: do we need a separate measurement task here 

496 # for the post-psf_measure_psf step, since we only want to do PsfFlux 

497 # and GaussianFlux *after* we have a PSF? Maybe that's not relevant 

498 # once DM-39203 is merged? 

499 self.psf_repair.run(exposure=exposure, keepCRs=True) 

500 # Re-estimate the background during this detection step, so that 

501 # measurement uses the most accurate background-subtraction. 

502 detections = self.psf_detection.run(table=table, exposure=exposure, background=background) 

503 self.psf_source_measurement.run(detections.sources, exposure) 

504 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources) 

505 

506 # PSF is set on exposure; only return candidates for optional saving. 

507 return detections.sources, background, psf_result.cellSet 

508 

509 def _measure_aperture_correction(self, exposure, bright_sources): 

510 """Measure and set the ApCorrMap on the Exposure, using 

511 previously-measured bright sources. 

512 

513 Parameters 

514 ---------- 

515 exposure : `lsst.afw.image.Exposure` 

516 Exposure to set the ApCorrMap on. 

517 bright_sources : `lsst.afw.table.SourceCatalog` 

518 Catalog of detected bright sources; modified to include columns 

519 necessary for point source determination for the aperture correction 

520 calculation. 

521 """ 

522 result = self.measure_aperture_correction.run(exposure, bright_sources) 

523 exposure.setApCorrMap(result.apCorrMap) 

524 

525 def _find_stars(self, exposure, background): 

526 """Detect stars on an exposure that has a PSF model, and measure their 

527 PSF, circular aperture, compensated gaussian fluxes. 

528 

529 Parameters 

530 ---------- 

531 exposure : `lsst.afw.image.Exposure` 

532 Exposure to set the ApCorrMap on. 

533 background : `lsst.afw.math.BackgroundList` 

534 Background that was fit to the exposure during detection; 

535 modified in-place during subsequent detection. 

536 

537 Returns 

538 ------- 

539 stars : `SourceCatalog` 

540 Sources that are very likely to be stars, with a limited set of 

541 measurements performed on them. 

542 """ 

543 table = afwTable.SourceTable.make(self.initial_stars_schema.schema) 

544 # Re-estimate the background during this detection step, so that 

545 # measurement uses the most accurate background-subtraction. 

546 detections = self.star_detection.run(table=table, exposure=exposure, background=background) 

547 sources = detections.sources 

548 

549 # Mask streaks 

550 self.star_mask_streaks.run(exposure) 

551 

552 # TODO investigation: Could this deblender throw away blends of non-PSF sources? 

553 self.star_deblend.run(exposure=exposure, sources=sources) 

554 # The deblender may not produce a contiguous catalog; ensure 

555 # contiguity for subsequent tasks. 

556 if not sources.isContiguous(): 

557 sources = sources.copy(deep=True) 

558 

559 # Measure everything, and use those results to select only stars. 

560 self.star_measurement.run(sources, exposure) 

561 self.star_apply_aperture_correction.run(sources, exposure.info.getApCorrMap()) 

562 self.star_catalog_calculation.run(sources) 

563 self.star_set_primary_flags.run(sources) 

564 

565 result = self.star_selector.run(sources) 

566 # The star selector may not produce a contiguous catalog. 

567 if not result.sourceCat.isContiguous(): 

568 return result.sourceCat.copy(deep=True) 

569 else: 

570 return result.sourceCat 

571 

572 def _fit_astrometry(self, exposure, stars): 

573 """Fit an astrometric model to the data and return the reference 

574 matches used in the fit, and the fitted WCS. 

575 

576 Parameters 

577 ---------- 

578 exposure : `lsst.afw.image.Exposure` 

579 Exposure that is being fit, to get PSF and other metadata from. 

580 Modified to add the fitted skyWcs. 

581 stars : `SourceCatalog` 

582 Good stars selected for use in calibration, with RA/Dec coordinates 

583 computed from the pixel positions and fitted WCS. 

584 

585 Returns 

586 ------- 

587 matches : `list` [`lsst.afw.table.ReferenceMatch`] 

588 Reference/stars matches used in the fit. 

589 """ 

590 result = self.astrometry.run(stars, exposure) 

591 return result.matches, result.matchMeta 

592 

593 def _fit_photometry(self, exposure, stars): 

594 """Fit a photometric model to the data and return the reference 

595 matches used in the fit, and the fitted PhotoCalib. 

596 

597 Parameters 

598 ---------- 

599 exposure : `lsst.afw.image.Exposure` 

600 Exposure that is being fit, to get PSF and other metadata from. 

601 Modified to be in nanojanksy units, with an assigned photoCalib 

602 identically 1. 

603 stars : `lsst.afw.table.SourceCatalog` 

604 Good stars selected for use in calibration. 

605 

606 Returns 

607 ------- 

608 calibrated_stars : `lsst.afw.table.SourceCatalog` 

609 Star catalog with flux/magnitude columns computed from the fitted 

610 photoCalib. 

611 matches : `list` [`lsst.afw.table.ReferenceMatch`] 

612 Reference/stars matches used in the fit. 

613 photoCalib : `lsst.afw.image.PhotoCalib` 

614 Photometric calibration that was fit to the star catalog. 

615 """ 

616 result = self.photometry.run(exposure, stars) 

617 calibrated_stars = result.photoCalib.calibrateCatalog(stars) 

618 exposure.maskedImage = result.photoCalib.calibrateImage(exposure.maskedImage) 

619 identity = afwImage.PhotoCalib(1.0, 

620 result.photoCalib.getCalibrationErr(), 

621 bbox=exposure.getBBox()) 

622 exposure.setPhotoCalib(identity) 

623 

624 return calibrated_stars, result.matches, result.matchMeta, result.photoCalib 

625 

626 def _summarize(self, exposure, stars, background): 

627 """Compute summary statistics on the exposure and update in-place the 

628 calibrations attached to it. 

629 

630 Parameters 

631 ---------- 

632 exposure : `lsst.afw.image.Exposure` 

633 Exposure that was calibrated, to get PSF and other metadata from. 

634 Modified to contain the computed summary statistics. 

635 stars : `SourceCatalog` 

636 Good stars selected used in calibration. 

637 background : `lsst.afw.math.BackgroundList` 

638 Background that was fit to the exposure during detection of the 

639 above stars. 

640 """ 

641 # TODO investigation: because this takes the photoCalib from the 

642 # exposure, photometric summary values may be "incorrect" (i.e. they 

643 # will reflect the ==1 nJy calibration on the exposure, not the 

644 # applied calibration). This needs to be checked. 

645 summary = self.compute_summary_stats.run(exposure, stars, background) 

646 exposure.info.setSummaryStats(summary)