Coverage for tests/test_calibrateImage.py: 16%

257 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-20 13:15 +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 unittest 

23import tempfile 

24 

25import astropy.units as u 

26from astropy.coordinates import SkyCoord 

27import numpy as np 

28 

29import lsst.afw.image as afwImage 

30import lsst.afw.table as afwTable 

31import lsst.daf.base 

32import lsst.daf.butler 

33import lsst.daf.butler.tests as butlerTests 

34import lsst.geom 

35import lsst.meas.algorithms 

36from lsst.meas.algorithms import testUtils 

37import lsst.meas.extensions.psfex 

38import lsst.meas.base 

39import lsst.meas.base.tests 

40import lsst.pipe.base.testUtils 

41from lsst.pipe.tasks.calibrateImage import CalibrateImageTask 

42import lsst.utils.tests 

43 

44 

45class CalibrateImageTaskTests(lsst.utils.tests.TestCase): 

46 

47 def setUp(self): 

48 # Different x/y dimensions so they're easy to distinguish in a plot, 

49 # and non-zero minimum, to help catch xy0 errors. 

50 bbox = lsst.geom.Box2I(lsst.geom.Point2I(5, 4), lsst.geom.Point2I(205, 184)) 

51 self.sky_center = lsst.geom.SpherePoint(245.0, -45.0, lsst.geom.degrees) 

52 self.photo_calib = 12.3 

53 dataset = lsst.meas.base.tests.TestDataset(bbox, crval=self.sky_center, calibration=self.photo_calib) 

54 # sqrt of area of a normalized 2d gaussian 

55 psf_scale = np.sqrt(4*np.pi*(dataset.psfShape.getDeterminantRadius())**2) 

56 noise = 10.0 # stddev of noise per pixel 

57 # Sources ordered from faintest to brightest. 

58 self.fluxes = np.array((6*noise*psf_scale, 

59 12*noise*psf_scale, 

60 45*noise*psf_scale, 

61 150*noise*psf_scale, 

62 400*noise*psf_scale, 

63 1000*noise*psf_scale)) 

64 self.centroids = np.array(((162, 22), 

65 (40, 70), 

66 (100, 160), 

67 (50, 120), 

68 (92, 35), 

69 (175, 154)), dtype=np.float32) 

70 for flux, centroid in zip(self.fluxes, self.centroids): 

71 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(centroid[0], centroid[1])) 

72 

73 # Bright extended source in the center of the image: should not appear 

74 # in any of the output catalogs. 

75 center = lsst.geom.Point2D(100, 100) 

76 shape = lsst.afw.geom.Quadrupole(8, 9, 3) 

77 dataset.addSource(instFlux=500*noise*psf_scale, centroid=center, shape=shape) 

78 

79 schema = dataset.makeMinimalSchema() 

80 self.truth_exposure, self.truth_cat = dataset.realize(noise=noise, schema=schema) 

81 # To make it look like a version=1 (nJy fluxes) refcat 

82 self.truth_cat = self.truth_exposure.photoCalib.calibrateCatalog(self.truth_cat) 

83 self.ref_loader = testUtils.MockReferenceObjectLoaderFromMemory([self.truth_cat]) 

84 metadata = lsst.daf.base.PropertyList() 

85 metadata.set("REFCAT_FORMAT_VERSION", 1) 

86 self.truth_cat.setMetadata(metadata) 

87 

88 # TODO: a cosmic ray (need to figure out how to insert a fake-CR) 

89 # self.truth_exposure.image.array[10, 10] = 100000 

90 # self.truth_exposure.variance.array[10, 10] = 100000/noise 

91 

92 # Copy the truth exposure, because CalibrateImage modifies the input. 

93 # Post-ISR ccds only contain: initial WCS, VisitInfo, filter 

94 self.exposure = afwImage.ExposureF(self.truth_exposure.maskedImage) 

95 self.exposure.setWcs(self.truth_exposure.wcs) 

96 self.exposure.info.setVisitInfo(self.truth_exposure.visitInfo) 

97 # "truth" filter, to match the "truth" refcat. 

98 self.exposure.setFilter(lsst.afw.image.FilterLabel(physical='truth', band="truth")) 

99 

100 # Test-specific configuration: 

101 self.config = CalibrateImageTask.ConfigClass() 

102 # We don't have many sources, so have to fit simpler models. 

103 self.config.psf_detection.background.approxOrderX = 1 

104 self.config.star_detection.background.approxOrderX = 1 

105 # Only insert 2 sky sources, for simplicity. 

106 self.config.star_sky_sources.nSources = 2 

107 # Use PCA psf fitter, as psfex fails if there are only 4 stars. 

108 self.config.psf_measure_psf.psfDeterminer = 'pca' 

109 # We don't have many test points, so can't match on complicated shapes. 

110 self.config.astrometry.matcher.numPointsForShape = 3 

111 # ApFlux has more noise than PsfFlux (the latter unrealistically small 

112 # in this test data), so we need to do magnitude rejection at higher 

113 # sigma, otherwise we can lose otherwise good sources. 

114 # TODO DM-39203: Once we are using Compensated Gaussian Fluxes, we 

115 # will use those fluxes here, and hopefully can remove this. 

116 self.config.astrometry.magnitudeOutlierRejectionNSigma = 9.0 

117 

118 # Make a realistic id generator so that output catalog ids are useful. 

119 # NOTE: The id generator is used to seed the noise replacer during 

120 # measurement, so changes to values here can have subtle effects on 

121 # the centroids and fluxes mesaured on the image, which might cause 

122 # tests to fail. 

123 data_id = lsst.daf.butler.DataCoordinate.standardize( 

124 instrument="I", 

125 visit=self.truth_exposure.visitInfo.id, 

126 detector=12, 

127 universe=lsst.daf.butler.DimensionUniverse(), 

128 ) 

129 self.config.id_generator.packer.name = "observation" 

130 self.config.id_generator.packer["observation"].n_observations = 10000 

131 self.config.id_generator.packer["observation"].n_detectors = 99 

132 self.config.id_generator.n_releases = 8 

133 self.config.id_generator.release_id = 2 

134 self.id_generator = self.config.id_generator.apply(data_id) 

135 

136 # Something about this test dataset prefers a larger threshold here. 

137 self.config.star_selector["science"].unresolved.maximum = 0.2 

138 

139 def _check_run(self, calibrate, result): 

140 """Test the result of CalibrateImage.run(). 

141 

142 Parameters 

143 ---------- 

144 calibrate : `lsst.pipe.tasks.calibrateImage.CalibrateImageTask` 

145 Configured task that had `run` called on it. 

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

147 Result of calling calibrate.run(). 

148 """ 

149 # Background should have 4 elements: 3 from compute_psf and one from 

150 # re-estimation during source detection. 

151 self.assertEqual(len(result.background), 4) 

152 

153 # Check that the summary statistics are reasonable. 

154 summary = result.output_exposure.info.getSummaryStats() 

155 self.assertFloatsAlmostEqual(summary.psfSigma, 2.0, rtol=1e-2) 

156 self.assertFloatsAlmostEqual(summary.ra, self.sky_center.getRa().asDegrees(), rtol=1e-7) 

157 self.assertFloatsAlmostEqual(summary.dec, self.sky_center.getDec().asDegrees(), rtol=1e-7) 

158 

159 # Returned photoCalib should be the applied value, not the ==1 one on the exposure. 

160 self.assertFloatsAlmostEqual(result.applied_photo_calib.getCalibrationMean(), 

161 self.photo_calib, rtol=2e-3) 

162 # Should have flux/magnitudes in the afw and astropy catalogs 

163 self.assertIn("slot_PsfFlux_flux", result.stars_footprints.schema) 

164 self.assertIn("slot_PsfFlux_mag", result.stars_footprints.schema) 

165 self.assertEqual(result.stars["slot_PsfFlux_flux"].unit, u.nJy) 

166 self.assertEqual(result.stars["slot_PsfFlux_mag"].unit, u.ABmag) 

167 

168 # Should have detected all S/N >= 10 sources plus 2 sky sources, whether 1 or 2 snaps. 

169 self.assertEqual(len(result.stars), 7) 

170 # Did the psf flags get propagated from the psf_stars catalog? 

171 self.assertEqual(result.stars["calib_psf_used"].sum(), 3) 

172 

173 # Check that all necessary fields are in the output. 

174 lsst.pipe.base.testUtils.assertValidOutput(calibrate, result) 

175 

176 def test_run(self): 

177 """Test that run() returns reasonable values to be butler put. 

178 """ 

179 calibrate = CalibrateImageTask(config=self.config) 

180 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

181 calibrate.photometry.match.setRefObjLoader(self.ref_loader) 

182 result = calibrate.run(exposures=self.exposure) 

183 

184 self._check_run(calibrate, result) 

185 

186 def test_run_2_snaps(self): 

187 """Test that run() returns reasonable values to be butler put, when 

188 passed two exposures to combine as snaps. 

189 """ 

190 calibrate = CalibrateImageTask(config=self.config) 

191 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

192 calibrate.photometry.match.setRefObjLoader(self.ref_loader) 

193 # Halve the flux in each exposure to get the expected visit sum. 

194 self.exposure.image /= 2 

195 self.exposure.variance /= 2 

196 result = calibrate.run(exposures=[self.exposure, self.exposure]) 

197 

198 self._check_run(calibrate, result) 

199 

200 def test_handle_snaps(self): 

201 calibrate = CalibrateImageTask(config=self.config) 

202 self.assertEqual(calibrate._handle_snaps(self.exposure), self.exposure) 

203 self.assertEqual(calibrate._handle_snaps((self.exposure, )), self.exposure) 

204 self.assertEqual(calibrate._handle_snaps(self.exposure), self.exposure) 

205 with self.assertRaisesRegex(RuntimeError, "Can only process 1 or 2 snaps, not 0."): 

206 calibrate._handle_snaps([]) 

207 with self.assertRaisesRegex(RuntimeError, "Can only process 1 or 2 snaps, not 3."): 

208 calibrate._handle_snaps(3*[self.exposure]) 

209 

210 def test_compute_psf(self): 

211 """Test that our brightest sources are found by _compute_psf(), 

212 that a PSF is assigned to the expopsure. 

213 """ 

214 calibrate = CalibrateImageTask(config=self.config) 

215 psf_stars, background, candidates = calibrate._compute_psf(self.exposure, self.id_generator) 

216 

217 # Catalog ids should be very large from this id generator. 

218 self.assertTrue(all(psf_stars['id'] > 1000000000)) 

219 

220 # Background should have 3 elements: initial subtraction, and two from 

221 # re-estimation during the two detection passes. 

222 self.assertEqual(len(background), 3) 

223 

224 # Only the point-sources with S/N > 50 should be in this output. 

225 self.assertEqual(psf_stars["calib_psf_used"].sum(), 3) 

226 # Sort in order of brightness, to easily compare with expected positions. 

227 psf_stars.sort(psf_stars.getPsfFluxSlot().getMeasKey()) 

228 for record, flux, center in zip(psf_stars[::-1], self.fluxes, self.centroids[self.fluxes > 50]): 

229 self.assertFloatsAlmostEqual(record.getX(), center[0], rtol=0.01) 

230 self.assertFloatsAlmostEqual(record.getY(), center[1], rtol=0.01) 

231 # PsfFlux should match the values inserted. 

232 self.assertFloatsAlmostEqual(record["slot_PsfFlux_instFlux"], flux, rtol=0.01) 

233 

234 # TODO: While debugging DM-32701, we're using PCA instead of psfex. 

235 # Check that we got a useable PSF. 

236 # self.assertIsInstance(self.exposure.psf, lsst.meas.extensions.psfex.PsfexPsf) 

237 self.assertIsInstance(self.exposure.psf, lsst.meas.algorithms.PcaPsf) 

238 # TestDataset sources have PSF radius=2 pixels. 

239 radius = self.exposure.psf.computeShape(self.exposure.psf.getAveragePosition()).getDeterminantRadius() 

240 self.assertFloatsAlmostEqual(radius, 2.0, rtol=1e-2) 

241 

242 # To look at images for debugging (`setup display_ds9` and run ds9): 

243 # import lsst.afw.display 

244 # display = lsst.afw.display.getDisplay() 

245 # display.mtv(self.exposure) 

246 

247 def test_measure_aperture_correction(self): 

248 """Test that _measure_aperture_correction() assigns an ApCorrMap to the 

249 exposure. 

250 """ 

251 calibrate = CalibrateImageTask(config=self.config) 

252 psf_stars, background, candidates = calibrate._compute_psf(self.exposure, self.id_generator) 

253 

254 # First check that the exposure doesn't have an ApCorrMap. 

255 self.assertIsNone(self.exposure.apCorrMap) 

256 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

257 self.assertIsInstance(self.exposure.apCorrMap, afwImage.ApCorrMap) 

258 

259 def test_find_stars(self): 

260 """Test that _find_stars() correctly identifies the S/N>10 stars 

261 in the image and returns them in the output catalog. 

262 """ 

263 calibrate = CalibrateImageTask(config=self.config) 

264 psf_stars, background, candidates = calibrate._compute_psf(self.exposure, self.id_generator) 

265 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

266 

267 stars = calibrate._find_stars(self.exposure, background, self.id_generator) 

268 

269 # Catalog ids should be very large from this id generator. 

270 self.assertTrue(all(stars['id'] > 1000000000)) 

271 

272 # Background should have 4 elements: 3 from compute_psf and one from 

273 # re-estimation during source detection. 

274 self.assertEqual(len(background), 4) 

275 

276 # Only 5 psf-like sources with S/N>10 should be in the output catalog, 

277 # plus two sky sources. 

278 self.assertEqual(len(stars), 7) 

279 self.assertTrue(stars.isContiguous()) 

280 # Sort in order of brightness, to easily compare with expected positions. 

281 stars.sort(stars.getPsfFluxSlot().getMeasKey()) 

282 for record, flux, center in zip(stars[::-1], self.fluxes, self.centroids[self.fluxes > 50]): 

283 self.assertFloatsAlmostEqual(record.getX(), center[0], rtol=0.01) 

284 self.assertFloatsAlmostEqual(record.getY(), center[1], rtol=0.01) 

285 self.assertFloatsAlmostEqual(record["slot_PsfFlux_instFlux"], flux, rtol=0.01) 

286 

287 def test_astrometry(self): 

288 """Test that the fitted WCS gives good catalog coordinates. 

289 """ 

290 calibrate = CalibrateImageTask(config=self.config) 

291 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

292 psf_stars, background, candidates = calibrate._compute_psf(self.exposure, self.id_generator) 

293 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

294 stars = calibrate._find_stars(self.exposure, background, self.id_generator) 

295 

296 calibrate._fit_astrometry(self.exposure, stars) 

297 

298 # Check that we got reliable matches with the truth coordinates. 

299 sky = stars["sky_source"] 

300 fitted = SkyCoord(stars[~sky]['coord_ra'], stars[~sky]['coord_dec'], unit="radian") 

301 truth = SkyCoord(self.truth_cat['coord_ra'], self.truth_cat['coord_dec'], unit="radian") 

302 idx, d2d, _ = fitted.match_to_catalog_sky(truth) 

303 np.testing.assert_array_less(d2d.to_value(u.milliarcsecond), 35.0) 

304 

305 def test_photometry(self): 

306 """Test that the fitted photoCalib matches the one we generated, 

307 and that the exposure is calibrated. 

308 """ 

309 calibrate = CalibrateImageTask(config=self.config) 

310 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

311 calibrate.photometry.match.setRefObjLoader(self.ref_loader) 

312 psf_stars, background, candidates = calibrate._compute_psf(self.exposure, self.id_generator) 

313 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

314 stars = calibrate._find_stars(self.exposure, background, self.id_generator) 

315 calibrate._fit_astrometry(self.exposure, stars) 

316 

317 stars, matches, meta, photoCalib = calibrate._fit_photometry(self.exposure, stars) 

318 

319 # NOTE: With this test data, PhotoCalTask returns calibrationErr==0, 

320 # so we can't check that the photoCal error has been set. 

321 self.assertFloatsAlmostEqual(photoCalib.getCalibrationMean(), self.photo_calib, rtol=2e-3) 

322 # The exposure should be calibrated by the applied photoCalib. 

323 self.assertFloatsAlmostEqual(self.exposure.image.array/self.truth_exposure.image.array, 

324 self.photo_calib, rtol=2e-3) 

325 # PhotoCalib on the exposure must be identically 1. 

326 self.assertEqual(self.exposure.photoCalib.getCalibrationMean(), 1.0) 

327 

328 # Check that we got reliable magnitudes and fluxes vs. truth, ignoring 

329 # sky sources. 

330 sky = stars["sky_source"] 

331 fitted = SkyCoord(stars[~sky]['coord_ra'], stars[~sky]['coord_dec'], unit="radian") 

332 truth = SkyCoord(self.truth_cat['coord_ra'], self.truth_cat['coord_dec'], unit="radian") 

333 idx, _, _ = fitted.match_to_catalog_sky(truth) 

334 # Because the input variance image does not include contributions from 

335 # the sources, we can't use fluxErr as a bound on the measurement 

336 # quality here. 

337 self.assertFloatsAlmostEqual(stars[~sky]['slot_PsfFlux_flux'], 

338 self.truth_cat['truth_flux'][idx], 

339 rtol=0.1) 

340 self.assertFloatsAlmostEqual(stars[~sky]['slot_PsfFlux_mag'], 

341 self.truth_cat['truth_mag'][idx], 

342 rtol=0.01) 

343 

344 def test_match_psf_stars(self): 

345 """Test that _match_psf_stars() flags the correct stars as psf stars 

346 and candidates. 

347 """ 

348 calibrate = CalibrateImageTask(config=self.config) 

349 psf_stars, background, candidates = calibrate._compute_psf(self.exposure, self.id_generator) 

350 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

351 stars = calibrate._find_stars(self.exposure, background, self.id_generator) 

352 

353 # There should be no psf-related flags set at first. 

354 self.assertEqual(stars["calib_psf_candidate"].sum(), 0) 

355 self.assertEqual(stars["calib_psf_used"].sum(), 0) 

356 self.assertEqual(stars["calib_psf_reserved"].sum(), 0) 

357 

358 # Reorder stars to be out of order with psf_stars (putting the sky 

359 # sources in front); this tests that I get the indexing right. 

360 stars.sort(stars.getCentroidSlot().getMeasKey().getX()) 

361 stars = stars.copy(deep=True) 

362 # Re-number the ids: the matcher requires sorted ids: this is always 

363 # true in the code itself, but we've permuted them by sorting on 

364 # flux. We don't care what the actual ids themselves are here. 

365 stars["id"] = np.arange(len(stars)) 

366 

367 calibrate._match_psf_stars(psf_stars, stars) 

368 

369 # Check that the three brightest stars have the psf flags transfered 

370 # from the psf_stars catalog by sorting in order of brightness. 

371 stars.sort(stars.getPsfFluxSlot().getMeasKey()) 

372 # sort() above leaves the catalog non-contiguous. 

373 stars = stars.copy(deep=True) 

374 np.testing.assert_array_equal(stars["calib_psf_candidate"], 

375 [False, False, False, False, True, True, True]) 

376 np.testing.assert_array_equal(stars["calib_psf_used"], [False, False, False, False, True, True, True]) 

377 # Too few sources to reserve any in these tests. 

378 self.assertEqual(stars["calib_psf_reserved"].sum(), 0) 

379 

380 def test_match_psf_stars_no_matches(self): 

381 """Check that _match_psf_stars handles the case of no cross-matches. 

382 """ 

383 calibrate = CalibrateImageTask(config=self.config) 

384 # Make two catalogs that cannot have matches. 

385 stars = self.truth_cat[2:].copy(deep=True) 

386 psf_stars = self.truth_cat[:2].copy(deep=True) 

387 

388 with self.assertRaisesRegex(RuntimeError, "0 psf_stars out of 2 matched"): 

389 calibrate._match_psf_stars(psf_stars, stars) 

390 

391 

392class CalibrateImageTaskRunQuantumTests(lsst.utils.tests.TestCase): 

393 """Tests of ``CalibrateImageTask.runQuantum``, which need a test butler, 

394 but do not need real images. 

395 """ 

396 def setUp(self): 

397 instrument = "testCam" 

398 exposure0 = 101 

399 exposure1 = 101 

400 visit = 100101 

401 detector = 42 

402 

403 # Create a and populate a test butler for runQuantum tests. 

404 self.repo_path = tempfile.TemporaryDirectory(ignore_cleanup_errors=True) 

405 self.repo = butlerTests.makeTestRepo(self.repo_path.name) 

406 

407 # A complete instrument record is necessary for the id generator. 

408 instrumentRecord = self.repo.dimensions["instrument"].RecordClass( 

409 name=instrument, visit_max=1e6, exposure_max=1e6, detector_max=128, 

410 class_name="lsst.obs.base.instrument_tests.DummyCam", 

411 ) 

412 self.repo.registry.syncDimensionData("instrument", instrumentRecord) 

413 

414 # dataIds for fake data 

415 butlerTests.addDataIdValue(self.repo, "exposure", exposure0) 

416 butlerTests.addDataIdValue(self.repo, "exposure", exposure1) 

417 butlerTests.addDataIdValue(self.repo, "visit", visit) 

418 butlerTests.addDataIdValue(self.repo, "detector", detector) 

419 

420 # inputs 

421 butlerTests.addDatasetType(self.repo, "postISRCCD", {"instrument", "exposure", "detector"}, 

422 "ExposureF") 

423 butlerTests.addDatasetType(self.repo, "gaia_dr3_20230707", {"htm7"}, "SimpleCatalog") 

424 butlerTests.addDatasetType(self.repo, "ps1_pv3_3pi_20170110", {"htm7"}, "SimpleCatalog") 

425 

426 # outputs 

427 butlerTests.addDatasetType(self.repo, "initial_pvi", {"instrument", "visit", "detector"}, 

428 "ExposureF") 

429 butlerTests.addDatasetType(self.repo, "initial_stars_footprints_detector", 

430 {"instrument", "visit", "detector"}, 

431 "SourceCatalog") 

432 butlerTests.addDatasetType(self.repo, "initial_stars_detector", 

433 {"instrument", "visit", "detector"}, 

434 "ArrowAstropy") 

435 butlerTests.addDatasetType(self.repo, "initial_photoCalib_detector", 

436 {"instrument", "visit", "detector"}, 

437 "PhotoCalib") 

438 # optional outputs 

439 butlerTests.addDatasetType(self.repo, "initial_pvi_background", {"instrument", "visit", "detector"}, 

440 "Background") 

441 butlerTests.addDatasetType(self.repo, "initial_psf_stars_footprints_detector", 

442 {"instrument", "visit", "detector"}, 

443 "SourceCatalog") 

444 butlerTests.addDatasetType(self.repo, "initial_psf_stars_detector", 

445 {"instrument", "visit", "detector"}, 

446 "ArrowAstropy") 

447 butlerTests.addDatasetType(self.repo, 

448 "initial_astrometry_match_detector", 

449 {"instrument", "visit", "detector"}, 

450 "Catalog") 

451 butlerTests.addDatasetType(self.repo, 

452 "initial_photometry_match_detector", 

453 {"instrument", "visit", "detector"}, 

454 "Catalog") 

455 

456 # dataIds 

457 self.exposure0_id = self.repo.registry.expandDataId( 

458 {"instrument": instrument, "exposure": exposure0, "detector": detector}) 

459 self.exposure1_id = self.repo.registry.expandDataId( 

460 {"instrument": instrument, "exposure": exposure1, "detector": detector}) 

461 self.visit_id = self.repo.registry.expandDataId( 

462 {"instrument": instrument, "visit": visit, "detector": detector}) 

463 self.htm_id = self.repo.registry.expandDataId({"htm7": 42}) 

464 

465 # put empty data 

466 self.butler = butlerTests.makeTestCollection(self.repo) 

467 self.butler.put(afwImage.ExposureF(), "postISRCCD", self.exposure0_id) 

468 self.butler.put(afwTable.SimpleCatalog(), "gaia_dr3_20230707", self.htm_id) 

469 self.butler.put(afwTable.SimpleCatalog(), "ps1_pv3_3pi_20170110", self.htm_id) 

470 

471 def tearDown(self): 

472 self.repo_path.cleanup() 

473 

474 def test_runQuantum(self): 

475 task = CalibrateImageTask() 

476 lsst.pipe.base.testUtils.assertValidInitOutput(task) 

477 

478 quantum = lsst.pipe.base.testUtils.makeQuantum( 

479 task, self.butler, self.visit_id, 

480 {"exposures": [self.exposure0_id], 

481 "astrometry_ref_cat": [self.htm_id], 

482 "photometry_ref_cat": [self.htm_id], 

483 # outputs 

484 "output_exposure": self.visit_id, 

485 "stars": self.visit_id, 

486 "stars_footprints": self.visit_id, 

487 "background": self.visit_id, 

488 "psf_stars": self.visit_id, 

489 "psf_stars_footprints": self.visit_id, 

490 "applied_photo_calib": self.visit_id, 

491 "initial_pvi_background": self.visit_id, 

492 "astrometry_matches": self.visit_id, 

493 "photometry_matches": self.visit_id, 

494 }) 

495 mock_run = lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum) 

496 

497 # Ensure the reference loaders have been configured. 

498 self.assertEqual(task.astrometry.refObjLoader.name, "gaia_dr3_20230707") 

499 self.assertEqual(task.photometry.match.refObjLoader.name, "ps1_pv3_3pi_20170110") 

500 # Check that the proper kwargs are passed to run(). 

501 self.assertEqual(mock_run.call_args.kwargs.keys(), {"exposures", "id_generator"}) 

502 

503 def test_runQuantum_2_snaps(self): 

504 task = CalibrateImageTask() 

505 lsst.pipe.base.testUtils.assertValidInitOutput(task) 

506 

507 quantum = lsst.pipe.base.testUtils.makeQuantum( 

508 task, self.butler, self.visit_id, 

509 {"exposures": [self.exposure0_id, self.exposure1_id], 

510 "astrometry_ref_cat": [self.htm_id], 

511 "photometry_ref_cat": [self.htm_id], 

512 # outputs 

513 "output_exposure": self.visit_id, 

514 "stars": self.visit_id, 

515 "stars_footprints": self.visit_id, 

516 "background": self.visit_id, 

517 "psf_stars": self.visit_id, 

518 "psf_stars_footprints": self.visit_id, 

519 "applied_photo_calib": self.visit_id, 

520 "initial_pvi_background": self.visit_id, 

521 "astrometry_matches": self.visit_id, 

522 "photometry_matches": self.visit_id, 

523 }) 

524 mock_run = lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum) 

525 

526 # Ensure the reference loaders have been configured. 

527 self.assertEqual(task.astrometry.refObjLoader.name, "gaia_dr3_20230707") 

528 self.assertEqual(task.photometry.match.refObjLoader.name, "ps1_pv3_3pi_20170110") 

529 # Check that the proper kwargs are passed to run(). 

530 self.assertEqual(mock_run.call_args.kwargs.keys(), {"exposures", "id_generator"}) 

531 

532 def test_runQuantum_no_optional_outputs(self): 

533 config = CalibrateImageTask.ConfigClass() 

534 config.optional_outputs = None 

535 task = CalibrateImageTask(config=config) 

536 lsst.pipe.base.testUtils.assertValidInitOutput(task) 

537 

538 quantum = lsst.pipe.base.testUtils.makeQuantum( 

539 task, self.butler, self.visit_id, 

540 {"exposures": [self.exposure0_id], 

541 "astrometry_ref_cat": [self.htm_id], 

542 "photometry_ref_cat": [self.htm_id], 

543 # outputs 

544 "output_exposure": self.visit_id, 

545 "stars": self.visit_id, 

546 "stars_footprints": self.visit_id, 

547 "applied_photo_calib": self.visit_id, 

548 "background": self.visit_id, 

549 }) 

550 mock_run = lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum) 

551 

552 # Ensure the reference loaders have been configured. 

553 self.assertEqual(task.astrometry.refObjLoader.name, "gaia_dr3_20230707") 

554 self.assertEqual(task.photometry.match.refObjLoader.name, "ps1_pv3_3pi_20170110") 

555 # Check that the proper kwargs are passed to run(). 

556 self.assertEqual(mock_run.call_args.kwargs.keys(), {"exposures", "id_generator"}) 

557 

558 def test_lintConnections(self): 

559 """Check that the connections are self-consistent. 

560 """ 

561 Connections = CalibrateImageTask.ConfigClass.ConnectionsClass 

562 lsst.pipe.base.testUtils.lintConnections(Connections) 

563 

564 

565def setup_module(module): 

566 lsst.utils.tests.init() 

567 

568 

569class MemoryTestCase(lsst.utils.tests.MemoryTestCase): 

570 pass 

571 

572 

573if __name__ == "__main__": 573 ↛ 574line 573 didn't jump to line 574, because the condition on line 573 was never true

574 lsst.utils.tests.init() 

575 unittest.main()