Coverage for tests/test_calibrateImage.py: 16%

247 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-19 02:21 -0700

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.tests as butlerTests 

33import lsst.geom 

34import lsst.meas.algorithms 

35from lsst.meas.algorithms import testUtils 

36import lsst.meas.extensions.psfex 

37import lsst.meas.base.tests 

38import lsst.pipe.base.testUtils 

39from lsst.pipe.tasks.calibrateImage import CalibrateImageTask 

40import lsst.utils.tests 

41 

42 

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

44 

45 def setUp(self): 

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

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

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

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

50 self.photo_calib = 12.3 

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

52 # sqrt of area of a normalized 2d gaussian 

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

54 noise = 10.0 # stddev of noise per pixel 

55 # Sources ordered from faintest to brightest. 

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

57 12*noise*psf_scale, 

58 45*noise*psf_scale, 

59 150*noise*psf_scale, 

60 400*noise*psf_scale, 

61 1000*noise*psf_scale)) 

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

63 (40, 70), 

64 (100, 160), 

65 (50, 120), 

66 (92, 35), 

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

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

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

70 

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

72 # in any of the output catalogs. 

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

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

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

76 

77 schema = dataset.makeMinimalSchema() 

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

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

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

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

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

83 metadata.set("REFCAT_FORMAT_VERSION", 1) 

84 self.truth_cat.setMetadata(metadata) 

85 

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

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

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

89 

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

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

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

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

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

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

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

97 

98 # Test-specific configuration: 

99 self.config = CalibrateImageTask.ConfigClass() 

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

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

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

103 # Only insert 2 sky sources, for simplicity. 

104 self.config.star_sky_sources.nSources = 2 

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

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

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

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

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

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

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

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

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

114 self.config.astrometry.magnitudeOutlierRejectionNSigma = 9.0 

115 

116 # find_stars needs an id generator. 

117 self.id_generator = lsst.meas.base.IdGenerator() 

118 

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

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

121 

122 def _check_run(self, calibrate, result): 

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

124 

125 Parameters 

126 ---------- 

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

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

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

130 Result of calling calibrate.run(). 

131 """ 

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

133 # re-estimation during source detection. 

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

135 

136 # Check that the summary statistics are reasonable. 

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

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

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

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

141 

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

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

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

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

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

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

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

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

150 

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

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

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

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

155 

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

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

158 

159 def test_run(self): 

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

161 """ 

162 calibrate = CalibrateImageTask(config=self.config) 

163 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

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

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

166 

167 self._check_run(calibrate, result) 

168 

169 def test_run_2_snaps(self): 

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

171 passed two exposures to combine as snaps. 

172 """ 

173 calibrate = CalibrateImageTask(config=self.config) 

174 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

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

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

177 self.exposure.image /= 2 

178 self.exposure.variance /= 2 

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

180 

181 self._check_run(calibrate, result) 

182 

183 def test_handle_snaps(self): 

184 calibrate = CalibrateImageTask(config=self.config) 

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

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

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

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

189 calibrate._handle_snaps([]) 

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

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

192 

193 def test_compute_psf(self): 

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

195 that a PSF is assigned to the expopsure. 

196 """ 

197 calibrate = CalibrateImageTask(config=self.config) 

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

199 

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

201 # re-estimation during the two detection passes. 

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

203 

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

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

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

207 psf_stars.sort(psf_stars.getPsfFluxSlot().getMeasKey()) 

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

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

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

211 # PsfFlux should match the values inserted. 

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

213 

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

215 # Check that we got a useable PSF. 

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

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

218 # TestDataset sources have PSF radius=2 pixels. 

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

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

221 

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

223 # import lsst.afw.display 

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

225 # display.mtv(self.exposure) 

226 

227 def test_measure_aperture_correction(self): 

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

229 exposure. 

230 """ 

231 calibrate = CalibrateImageTask(config=self.config) 

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

233 

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

235 self.assertIsNone(self.exposure.apCorrMap) 

236 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

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

238 

239 def test_find_stars(self): 

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

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

242 """ 

243 calibrate = CalibrateImageTask(config=self.config) 

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

245 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

246 

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

248 

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

250 # re-estimation during source detection. 

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

252 

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

254 # plus two sky sources. 

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

256 self.assertTrue(stars.isContiguous()) 

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

258 stars.sort(stars.getPsfFluxSlot().getMeasKey()) 

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

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

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

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

263 

264 def test_astrometry(self): 

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

266 """ 

267 calibrate = CalibrateImageTask(config=self.config) 

268 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

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

270 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

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

272 

273 calibrate._fit_astrometry(self.exposure, stars) 

274 

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

276 sky = stars["sky_source"] 

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

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

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

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

281 

282 def test_photometry(self): 

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

284 and that the exposure is calibrated. 

285 """ 

286 calibrate = CalibrateImageTask(config=self.config) 

287 calibrate.astrometry.setRefObjLoader(self.ref_loader) 

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

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

290 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

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

292 calibrate._fit_astrometry(self.exposure, stars) 

293 

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

295 

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

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

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

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

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

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

302 # PhotoCalib on the exposure must be identically 1. 

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

304 

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

306 # sky sources. 

307 sky = stars["sky_source"] 

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

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

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

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

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

313 # quality here. 

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

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

316 rtol=0.1) 

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

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

319 rtol=0.01) 

320 

321 def test_match_psf_stars(self): 

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

323 and candidates. 

324 """ 

325 calibrate = CalibrateImageTask(config=self.config) 

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

327 calibrate._measure_aperture_correction(self.exposure, psf_stars) 

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

329 

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

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

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

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

334 

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

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

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

338 stars = stars.copy(deep=True) 

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

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

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

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

343 

344 calibrate._match_psf_stars(psf_stars, stars) 

345 

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

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

348 stars.sort(stars.getPsfFluxSlot().getMeasKey()) 

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

350 stars = stars.copy(deep=True) 

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

352 [False, False, False, False, True, True, True]) 

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

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

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

356 

357 def test_match_psf_stars_no_matches(self): 

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

359 """ 

360 calibrate = CalibrateImageTask(config=self.config) 

361 # Make two catalogs that cannot have matches. 

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

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

364 

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

366 calibrate._match_psf_stars(psf_stars, stars) 

367 

368 

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

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

371 but do not need real images. 

372 """ 

373 def setUp(self): 

374 instrument = "testCam" 

375 exposure0 = 101 

376 exposure1 = 101 

377 visit = 100101 

378 detector = 42 

379 

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

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

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

383 

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

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

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

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

388 ) 

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

390 

391 # dataIds for fake data 

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

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

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

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

396 

397 # inputs 

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

399 "ExposureF") 

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

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

402 

403 # outputs 

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

405 "ExposureF") 

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

407 {"instrument", "visit", "detector"}, 

408 "SourceCatalog") 

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

410 {"instrument", "visit", "detector"}, 

411 "ArrowAstropy") 

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

413 {"instrument", "visit", "detector"}, 

414 "PhotoCalib") 

415 # optional outputs 

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

417 "Background") 

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

419 {"instrument", "visit", "detector"}, 

420 "SourceCatalog") 

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

422 {"instrument", "visit", "detector"}, 

423 "ArrowAstropy") 

424 butlerTests.addDatasetType(self.repo, 

425 "initial_astrometry_match_detector", 

426 {"instrument", "visit", "detector"}, 

427 "Catalog") 

428 butlerTests.addDatasetType(self.repo, 

429 "initial_photometry_match_detector", 

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

431 "Catalog") 

432 

433 # dataIds 

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

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

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

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

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

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

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

441 

442 # put empty data 

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

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

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

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

447 

448 def tearDown(self): 

449 self.repo_path.cleanup() 

450 

451 def test_runQuantum(self): 

452 task = CalibrateImageTask() 

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

454 

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

456 task, self.butler, self.visit_id, 

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

458 "astrometry_ref_cat": [self.htm_id], 

459 "photometry_ref_cat": [self.htm_id], 

460 # outputs 

461 "output_exposure": self.visit_id, 

462 "stars": self.visit_id, 

463 "stars_footprints": self.visit_id, 

464 "background": self.visit_id, 

465 "psf_stars": self.visit_id, 

466 "psf_stars_footprints": self.visit_id, 

467 "applied_photo_calib": self.visit_id, 

468 "initial_pvi_background": self.visit_id, 

469 "astrometry_matches": self.visit_id, 

470 "photometry_matches": self.visit_id, 

471 }) 

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

473 

474 # Ensure the reference loaders have been configured. 

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

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

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

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

479 

480 def test_runQuantum_2_snaps(self): 

481 task = CalibrateImageTask() 

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

483 

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

485 task, self.butler, self.visit_id, 

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

487 "astrometry_ref_cat": [self.htm_id], 

488 "photometry_ref_cat": [self.htm_id], 

489 # outputs 

490 "output_exposure": self.visit_id, 

491 "stars": self.visit_id, 

492 "stars_footprints": self.visit_id, 

493 "background": self.visit_id, 

494 "psf_stars": self.visit_id, 

495 "psf_stars_footprints": self.visit_id, 

496 "applied_photo_calib": self.visit_id, 

497 "initial_pvi_background": self.visit_id, 

498 "astrometry_matches": self.visit_id, 

499 "photometry_matches": self.visit_id, 

500 }) 

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

502 

503 # Ensure the reference loaders have been configured. 

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

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

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

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

508 

509 def test_runQuantum_no_optional_outputs(self): 

510 config = CalibrateImageTask.ConfigClass() 

511 config.optional_outputs = None 

512 task = CalibrateImageTask(config=config) 

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

514 

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

516 task, self.butler, self.visit_id, 

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

518 "astrometry_ref_cat": [self.htm_id], 

519 "photometry_ref_cat": [self.htm_id], 

520 # outputs 

521 "output_exposure": self.visit_id, 

522 "stars": self.visit_id, 

523 "stars_footprints": self.visit_id, 

524 "applied_photo_calib": self.visit_id, 

525 "background": self.visit_id, 

526 }) 

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

528 

529 # Ensure the reference loaders have been configured. 

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

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

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

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

534 

535 def test_lintConnections(self): 

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

537 """ 

538 Connections = CalibrateImageTask.ConfigClass.ConnectionsClass 

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

540 

541 

542def setup_module(module): 

543 lsst.utils.tests.init() 

544 

545 

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

547 pass 

548 

549 

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

551 lsst.utils.tests.init() 

552 unittest.main()