Coverage for tests / test_reprocess_visit_image.py: 12%

253 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-23 08:56 +0000

1# This file is part of drp_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 tempfile 

23import unittest 

24 

25import astropy.table 

26import numpy as np 

27 

28import lsst.afw.image 

29import lsst.afw.math 

30import lsst.afw.table 

31import lsst.daf.butler.tests as butlerTests 

32import lsst.geom 

33import lsst.meas.algorithms 

34import lsst.meas.base.tests 

35import lsst.pipe.base.testUtils 

36import lsst.utils.tests 

37from lsst.drp.tasks.reprocess_visit_image import ReprocessVisitImageTask 

38from lsst.pex.exceptions import InvalidParameterError 

39 

40 

41def make_visit_summary(summary=None, psf=None, wcs=None, photo_calib=None, detector=42): 

42 """Return a visit summary table with an entry for the given detector.""" 

43 if summary is None: 

44 schema = lsst.afw.table.ExposureTable.makeMinimalSchema() 

45 lsst.afw.image.ExposureSummaryStats.update_schema(schema) 

46 summary = lsst.afw.table.ExposureCatalog(schema) 

47 if summary.find(detector) is not None: 

48 raise RuntimeError(f"Detector {detector} already exists in visit summary table, can't re-add it.") 

49 

50 record = summary.addNew() 

51 record.setId(detector) 

52 record.setApCorrMap(lsst.afw.image.ApCorrMap()), 

53 

54 record.setPsf(psf) 

55 record.setPhotoCalib(photo_calib) 

56 

57 if wcs is not None: 

58 record.setWcs(wcs) 

59 else: 

60 crpix = lsst.geom.Box2D( 

61 lsst.geom.Box2D(lsst.geom.Point2D(0, 0), lsst.geom.Point2D(100, 100)) 

62 ).getCenter() 

63 crval = lsst.geom.SpherePoint(45.0, 45.0, lsst.geom.degrees) 

64 cdelt = 0.2 * lsst.geom.arcseconds 

65 wcs = lsst.afw.geom.makeSkyWcs( 

66 crpix=crpix, crval=crval, cdMatrix=lsst.afw.geom.makeCdMatrix(scale=cdelt) 

67 ) 

68 record.setWcs(wcs) 

69 

70 lsst.afw.image.ExposureSummaryStats().update_record(record), 

71 

72 return summary 

73 

74 

75class ReprocessVisitImageTaskTests(lsst.utils.tests.TestCase): 

76 def setUp(self): 

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

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

79 detector = 42 

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

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

82 self.photo_calib = 12.3 

83 dataset = lsst.meas.base.tests.TestDataset( 

84 bbox, 

85 crval=self.sky_center, 

86 calibration=self.photo_calib, 

87 detector=detector, 

88 # Force a large visitId, to test DM-49138. 

89 visitId=2**33, 

90 ) 

91 # sqrt of area of a normalized 2d gaussian 

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

93 noise = 10.0 # stddev of noise per pixel 

94 # Sources ordered from faintest to brightest. 

95 self.fluxes = np.array( 

96 ( 

97 6 * noise * psf_scale, 

98 12 * noise * psf_scale, 

99 45 * noise * psf_scale, 

100 150 * noise * psf_scale, 

101 400 * noise * psf_scale, 

102 1000 * noise * psf_scale, 

103 ) 

104 ) 

105 self.centroids = np.array( 

106 ((162, 25), (40, 70), (100, 160), (50, 120), (92, 35), (175, 154)), dtype=np.float32 

107 ) 

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

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

110 

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

112 # in any of the output catalogs. 

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

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

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

116 

117 schema = dataset.makeMinimalSchema() 

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

119 # Add a cosmic ray-like two hot pixels, to check CR removal. 

120 self.truth_exposure.image[60, 60] = 10000 

121 self.truth_exposure.image[60, 61] = 10000 

122 

123 # Make an exposure that looks like a PostISRCCD, to serve as the input. 

124 self.exposure = lsst.afw.image.ExposureF() 

125 self.exposure.maskedImage = self.truth_exposure.maskedImage.clone() 

126 self.exposure.mask.clearMaskPlane(self.exposure.mask.getMaskPlane("DETECTED")) 

127 self.exposure.mask.clearMaskPlane(self.exposure.mask.getMaskPlane("DETECTED_NEGATIVE")) 

128 # PostISRCCD will have a VisitInfo and Detector attached. 

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

130 self.exposure.info.setDetector(self.truth_exposure.getDetector()) 

131 

132 # Subtract a background from the truth exposure, so that the input 

133 # exposure still has the background in it. 

134 config = lsst.meas.algorithms.SubtractBackgroundTask.ConfigClass() 

135 # Don't really have a background, so have to fit simpler models. 

136 config.approxOrderX = 1 

137 task = lsst.meas.algorithms.SubtractBackgroundTask(config=config) 

138 self.background = task.run(self.truth_exposure).background 

139 self.visit_summary = make_visit_summary( 

140 psf=self.truth_exposure.psf, 

141 wcs=self.truth_exposure.wcs, 

142 photo_calib=self.truth_exposure.photoCalib, 

143 ) 

144 

145 # A catalog that looks like the output of finalizeCharacterization, 

146 # with a value set that we can test on the output. 

147 self.visit_catalog = self.truth_cat.asAstropy() 

148 self.visit_catalog.add_column( 

149 astropy.table.Column(data=np.zeros(len(self.visit_catalog)), name="calib_psf_used", dtype=bool) 

150 ) 

151 self.visit_catalog.add_column( 

152 astropy.table.Column( 

153 data=np.zeros(len(self.visit_catalog)), name="calib_psf_reserved", dtype=bool 

154 ) 

155 ) 

156 self.visit_catalog.add_column( 

157 astropy.table.Column( 

158 data=np.zeros(len(self.visit_catalog)), name="calib_psf_candidate", dtype=bool 

159 ) 

160 ) 

161 self.visit_catalog.add_column( 

162 astropy.table.Column(data=[detector] * len(self.visit_catalog), name="detector") 

163 ) 

164 # Marking faintest source, so it's easy to identify later. 

165 self.visit_catalog["calib_psf_used"][0] = True 

166 

167 # Test-specific configuration: 

168 self.config = ReprocessVisitImageTask.ConfigClass() 

169 # Don't really have a background, so have to fit simpler models. 

170 self.config.detection.background.approxOrderX = 1 

171 # Only insert 2 sky sources, for simplicity. 

172 self.config.sky_sources.nSources = 2 

173 

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

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

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

177 # the centroids and fluxes measured on the image, which might cause 

178 # tests to fail. 

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

180 instrument="I", 

181 visit=self.truth_exposure.visitInfo.id, 

182 detector=12, 

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

184 ) 

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

186 # Without the LSSTCam-specific visitId handler, we have to use a large 

187 # n_observation to fit visitId=2^33. 

188 self.config.id_generator.packer["observation"].n_observations = 2**35 

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

190 self.config.id_generator.n_releases = 8 

191 self.config.id_generator.release_id = 2 

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

193 

194 def test_run(self): 

195 

196 background_to_photometric_ratio_value = 1.1 

197 

198 for do_apply_flat_background_ratio in [False, True]: 

199 config = self.config 

200 config.do_apply_flat_background_ratio = do_apply_flat_background_ratio 

201 

202 if do_apply_flat_background_ratio: 

203 background_to_photometric_ratio = self.exposure.image.clone() 

204 background_to_photometric_ratio.array[:, :] = background_to_photometric_ratio_value 

205 else: 

206 background_to_photometric_ratio = None 

207 

208 task = ReprocessVisitImageTask(config=config) 

209 result = task.run( 

210 exposures=self.exposure.clone(), 

211 initial_photo_calib=self.truth_exposure.photoCalib, 

212 psf=self.truth_exposure.psf, 

213 background=self.background, 

214 ap_corr=lsst.afw.image.ApCorrMap(), 

215 photo_calib=self.truth_exposure.photoCalib, 

216 wcs=self.truth_exposure.wcs, 

217 calib_sources=self.visit_catalog, 

218 id_generator=self.id_generator, 

219 background_to_photometric_ratio=background_to_photometric_ratio, 

220 ) 

221 

222 calibrated = result.exposure.photoCalib.calibrateImage(result.exposure.maskedImage) 

223 self.assertImagesAlmostEqual(result.exposure.image, calibrated.image) 

224 self.assertImagesAlmostEqual(result.exposure.variance, calibrated.variance) 

225 self.assertEqual(result.exposure.psf, self.truth_exposure.psf) 

226 self.assertEqual(result.exposure.wcs, self.truth_exposure.wcs) 

227 # A calibrated exposure has PhotoCalib==1. 

228 self.assertNotEqual(result.exposure.photoCalib, self.truth_exposure.photoCalib) 

229 self.assertFloatsAlmostEqual(result.exposure.photoCalib.getCalibrationMean(), 1) 

230 

231 # All sources (plus sky sources) should have been detected. 

232 self.assertEqual(len(result.sources), len(self.truth_cat) + self.config.sky_sources.nSources) 

233 # Faintest non-sky source should be marked as used. 

234 flux_sorted = result.sources[result.sources.argsort("slot_CalibFlux_instFlux")] 

235 self.assertTrue(flux_sorted[~flux_sorted["sky_source"]]["calib_psf_used"][0]) 

236 # Test that the schema init-output agrees with the catalog output. 

237 self.assertEqual(task.sources_schema.schema, result.sources_footprints.schema) 

238 # The flux/instFlux ratio should be the LocalPhotoCalib value. 

239 for record in result.sources_footprints: 

240 self.assertAlmostEqual( 

241 record["base_PsfFlux_flux"] / record["base_PsfFlux_instFlux"], 

242 record["base_LocalPhotoCalib"], 

243 ) 

244 

245 if not do_apply_flat_background_ratio: 

246 result_false = result 

247 

248 self.assertFloatsAlmostEqual( 

249 result_false.sources_footprints["base_PsfFlux_instFlux"] 

250 / result.sources_footprints["base_PsfFlux_instFlux"], 

251 background_to_photometric_ratio_value, 

252 rtol=1e-6, 

253 ) 

254 

255 self.assertFloatsAlmostEqual( 

256 result_false.sources_footprints["base_PsfFlux_flux"] 

257 / result.sources_footprints["base_PsfFlux_flux"], 

258 background_to_photometric_ratio_value, 

259 rtol=1e-6, 

260 ) 

261 

262 # Prior to DM-49138, LSSTCam-style >32-bit visitIds were silently 

263 # down-cast to `0`. 

264 self.assertTrue((result.sources["visit"] == 2**33).all()) 

265 

266 self.assertEqual(result.exposure.metadata["BUNIT"], "nJy") 

267 

268 def test_update_masks(self): 

269 

270 config = self.config 

271 config.copyMaskPlanes = ["foo", "bar"] 

272 preliminary_mask_planes = ["bar", "TEST", "foobar"] 

273 bbox = self.exposure.getBBox() 

274 xy0 = bbox.getBegin() 

275 preliminary_mask = self.exposure.mask.clone() 

276 preliminary_mask.array *= 0 

277 width = 20 

278 for mp in preliminary_mask_planes: 

279 xy0.shift(lsst.geom.Extent2I(width, width)) 

280 subBox = lsst.geom.Box2I(xy0, lsst.geom.Extent2I(width, width)) 

281 bitmask = preliminary_mask.addMaskPlane(mp) 

282 preliminary_mask[subBox] |= 2**bitmask 

283 exposure = self.exposure.clone() 

284 xy0.shift(lsst.geom.Extent2I(width, width)) 

285 subBox = lsst.geom.Box2I(xy0, lsst.geom.Extent2I(width, width)) 

286 bitmask = exposure.mask.addMaskPlane("TEST") 

287 exposure.mask[subBox] |= 2**bitmask 

288 

289 task = ReprocessVisitImageTask(config=config) 

290 result = task.run( 

291 exposures=exposure, 

292 initial_photo_calib=self.truth_exposure.photoCalib, 

293 psf=self.truth_exposure.psf, 

294 background=self.background, 

295 ap_corr=lsst.afw.image.ApCorrMap(), 

296 photo_calib=self.truth_exposure.photoCalib, 

297 wcs=self.truth_exposure.wcs, 

298 calib_sources=self.visit_catalog, 

299 preliminary_mask=preliminary_mask, 

300 id_generator=self.id_generator, 

301 background_to_photometric_ratio=None, 

302 ) 

303 # A mask plane in copyMaskPlanes that is not actually in 

304 # preliminary_mask should not be added 

305 with self.assertRaises(InvalidParameterError): 

306 result.exposure.mask.getMaskPlane("foo") 

307 # ensure that masks from preliminary_mask that exist in copyMaskPlanes 

308 # have been copied, while existing masks not listed in copyMaskPlanes 

309 # are unchanged. 

310 barMask = result.exposure.mask.getPlaneBitMask("bar") 

311 resultSet = (result.exposure.mask.array & barMask) > 0 

312 prelimSet = (preliminary_mask.array & barMask) > 0 

313 self.assertTrue(np.all(resultSet == prelimSet)) 

314 detectedMask = result.exposure.mask.getPlaneBitMask("TEST") 

315 resultSet = (result.exposure.mask.array & detectedMask) > 0 

316 prelimSet = (preliminary_mask.array & detectedMask) > 0 

317 self.assertEqual(np.sum(resultSet != prelimSet), 2 * width**2) 

318 

319 

320class ReprocessVisitImageTaskRunQuantumTests(lsst.utils.tests.TestCase): 

321 """Tests of ``ReprocessVisitImageTask.runQuantum``, which need a test 

322 butler, but do not need real data. 

323 """ 

324 

325 def setUp(self): 

326 instrument = "testCam" 

327 exposure0 = 101 

328 exposure1 = 102 

329 visit = 100101 

330 detector = 42 

331 

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

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

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

335 

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

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

338 name=instrument, 

339 visit_max=1e6, 

340 exposure_max=1e6, 

341 detector_max=128, 

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

343 ) 

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

345 

346 # dataIds for fake data 

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

348 butlerTests.addDataIdValue(self.repo, "detector", detector + 1) 

349 butlerTests.addDataIdValue(self.repo, "detector", detector + 2) 

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

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

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

353 

354 # inputs 

355 butlerTests.addDatasetType( 

356 self.repo, "postISRCCD", {"instrument", "exposure", "detector"}, "ExposureF" 

357 ) 

358 butlerTests.addDatasetType(self.repo, "finalVisitSummary", {"instrument", "visit"}, "ExposureCatalog") 

359 butlerTests.addDatasetType( 

360 self.repo, "initial_pvi_background", {"instrument", "visit", "detector"}, "Background" 

361 ) 

362 butlerTests.addDatasetType( 

363 self.repo, "initial_photoCalib_detector", {"instrument", "visit", "detector"}, "PhotoCalib" 

364 ) 

365 butlerTests.addDatasetType(self.repo, "skyCorr", {"instrument", "visit", "detector"}, "Background") 

366 butlerTests.addDatasetType(self.repo, "finalized_src_table", {"instrument", "visit"}, "DataFrame") 

367 butlerTests.addDatasetType( 

368 self.repo, "background_to_photometric_ratio", {"instrument", "visit", "detector"}, "Image" 

369 ) 

370 

371 # outputs 

372 butlerTests.addDatasetType( 

373 self.repo, "source_schema", {"instrument", "visit", "detector"}, "SourceCatalog" 

374 ) 

375 butlerTests.addDatasetType(self.repo, "pvi", {"instrument", "visit", "detector"}, "ExposureF") 

376 butlerTests.addDatasetType( 

377 self.repo, "sources_footprints_detector", {"instrument", "visit", "detector"}, "SourceCatalog" 

378 ) 

379 butlerTests.addDatasetType( 

380 self.repo, "sources_detector", {"instrument", "visit", "detector"}, "ArrowAstropy" 

381 ) 

382 butlerTests.addDatasetType( 

383 self.repo, "pvi_background", {"instrument", "visit", "detector"}, "Background" 

384 ) 

385 butlerTests.addDatasetType( 

386 self.repo, "preliminary_visit_mask", {"instrument", "visit", "detector"}, "Mask" 

387 ) 

388 

389 # dataIds 

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

391 {"instrument": instrument, "exposure": exposure0, "detector": detector} 

392 ) 

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

394 {"instrument": instrument, "exposure": exposure1, "detector": detector} 

395 ) 

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

397 {"instrument": instrument, "visit": visit, "detector": detector} 

398 ) 

399 # Second id for testing on a detector that is not in visitSummary. 

400 self.visit1_id = self.repo.registry.expandDataId( 

401 {"instrument": instrument, "visit": visit, "detector": detector + 1} 

402 ) 

403 # Third id for testing on a detector that is in visitSummary but 

404 # has missing calibs. 

405 self.visit2_id = self.repo.registry.expandDataId( 

406 {"instrument": instrument, "visit": visit, "detector": detector + 2} 

407 ) 

408 self.visit_only_id = self.repo.registry.expandDataId({"instrument": instrument, "visit": visit}) 

409 

410 # put empty data 

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

412 self.butler.put(lsst.afw.image.ExposureF(), "postISRCCD", self.exposure0_id) 

413 self.butler.put(lsst.afw.image.ExposureF(), "postISRCCD", self.exposure1_id) 

414 self.butler.put(lsst.afw.image.Mask(), "preliminary_visit_mask", self.visit_id) 

415 control = lsst.afw.math.BackgroundControl(10, 10) 

416 background = lsst.afw.math.makeBackground(lsst.afw.image.ImageF(100, 100), control) 

417 self.butler.put(lsst.afw.image.PhotoCalib(10), "initial_photoCalib_detector", self.visit_id) 

418 self.butler.put(lsst.afw.image.PhotoCalib(10), "initial_photoCalib_detector", self.visit1_id) 

419 self.butler.put(lsst.afw.image.PhotoCalib(10), "initial_photoCalib_detector", self.visit2_id) 

420 self.butler.put(lsst.afw.math.BackgroundList(background), "initial_pvi_background", self.visit_id) 

421 self.butler.put(lsst.afw.math.BackgroundList(background), "initial_pvi_background", self.visit1_id) 

422 self.butler.put(lsst.afw.math.BackgroundList(background), "initial_pvi_background", self.visit2_id) 

423 self.butler.put(lsst.afw.math.BackgroundList(background), "skyCorr", self.visit_id) 

424 self.butler.put(lsst.afw.math.BackgroundList(background), "skyCorr", self.visit1_id) 

425 self.butler.put(lsst.afw.math.BackgroundList(background), "skyCorr", self.visit2_id) 

426 self.butler.put(lsst.afw.table.SourceCatalog().asAstropy(), "finalized_src_table", self.visit_only_id) 

427 self.butler.put(lsst.afw.image.ImageF(), "background_to_photometric_ratio", self.visit_id) 

428 self.butler.put(lsst.afw.image.ImageF(), "background_to_photometric_ratio", self.visit1_id) 

429 self.butler.put(lsst.afw.image.ImageF(), "background_to_photometric_ratio", self.visit2_id) 

430 # Make a simple single gaussian psf so that psf is not None in 

431 # finalVisitSummary table which would result in 

432 # UpstreamFailureNoWorkFound being raised in ReprocessVisitImageTask, 

433 # which will be independently tested with self.visit2_id. 

434 simple_psf = lsst.meas.algorithms.SingleGaussianPsf(11, 11, 2.0) 

435 photo_calib = lsst.afw.image.PhotoCalib(10, 0.5) 

436 visit_summary = make_visit_summary(psf=simple_psf, photo_calib=photo_calib, detector=detector) 

437 # Add a detector with an entry, but some required calibs set to None. 

438 visit_summary = make_visit_summary( 

439 summary=visit_summary, psf=None, photo_calib=None, detector=detector + 2 

440 ) 

441 self.butler.put(visit_summary, "finalVisitSummary", self.visit_only_id) 

442 

443 def tearDown(self): 

444 self.repo_path.cleanup() 

445 

446 def test_lintConnections(self): 

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

448 Connections = ReprocessVisitImageTask.ConfigClass.ConnectionsClass 

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

450 

451 def test_runQuantum(self): 

452 task = ReprocessVisitImageTask() 

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

454 

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

456 task, 

457 self.butler, 

458 self.visit_id, 

459 { 

460 "exposures": [self.exposure0_id], 

461 "preliminary_mask": self.visit_id, 

462 "visit_summary": self.visit_only_id, 

463 "initial_photo_calib": self.visit_id, 

464 "background_1": self.visit_id, 

465 "background_2": self.visit_id, 

466 "calib_sources": self.visit_only_id, 

467 "background_to_photometric_ratio": self.visit_id, 

468 # outputs 

469 "exposure": self.visit_id, 

470 "sources": self.visit_id, 

471 "sources_footprints": self.visit_id, 

472 "background": self.visit_id, 

473 }, 

474 ) 

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

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

477 self.assertEqual( 

478 mock_run.call_args.kwargs.keys(), 

479 { 

480 "exposures", 

481 "preliminary_mask", 

482 "initial_photo_calib", 

483 "psf", 

484 "background", 

485 "ap_corr", 

486 "photo_calib", 

487 "wcs", 

488 "calib_sources", 

489 "id_generator", 

490 "background_to_photometric_ratio", 

491 "result", 

492 }, 

493 ) 

494 

495 def test_runQuantum_no_detector_in_visit_summary(self): 

496 """Test how the task handles the detector not being in the input visit 

497 summary. 

498 """ 

499 task = ReprocessVisitImageTask() 

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

501 

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

503 task, 

504 self.butler, 

505 self.visit1_id, 

506 { 

507 "exposures": [self.exposure0_id], 

508 "preliminary_mask": self.visit_id, 

509 "visit_summary": self.visit_only_id, 

510 "initial_photo_calib": self.visit1_id, 

511 "background_1": self.visit1_id, 

512 "background_2": self.visit1_id, 

513 "calib_sources": self.visit_only_id, 

514 "background_to_photometric_ratio": self.visit_id, 

515 # outputs 

516 "exposure": self.visit1_id, 

517 "sources": self.visit1_id, 

518 "sources_footprints": self.visit1_id, 

519 "background": self.visit1_id, 

520 }, 

521 ) 

522 msg = " > no entry for the detector was found in the visit summary table" 

523 with self.assertRaisesRegex( 

524 lsst.pipe.base.UpstreamFailureNoWorkFound, f"Skipping reprocessing of detector 43 because:\n{msg}" 

525 ): 

526 lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum) 

527 

528 def test_runQuantum_missing_calibs_for_detector_in_visit_summary(self): 

529 """Test how the task handles the detector not being in the input visit 

530 summary. 

531 """ 

532 task = ReprocessVisitImageTask() 

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

534 

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

536 task, 

537 self.butler, 

538 self.visit2_id, 

539 { 

540 "exposures": [self.exposure0_id], 

541 "preliminary_mask": self.visit_id, 

542 "visit_summary": self.visit_only_id, 

543 "initial_photo_calib": self.visit2_id, 

544 "background_1": self.visit2_id, 

545 "background_2": self.visit2_id, 

546 "calib_sources": self.visit_only_id, 

547 "background_to_photometric_ratio": self.visit_id, 

548 # outputs 

549 "exposure": self.visit2_id, 

550 "sources": self.visit2_id, 

551 "sources_footprints": self.visit2_id, 

552 "background": self.visit2_id, 

553 }, 

554 ) 

555 lines = [ 

556 " > the PSF model for the detector is None", 

557 " > the photometric calibration model for the detector is None", 

558 ] 

559 msg = "\n".join(lines) 

560 with self.assertRaisesRegex( 

561 lsst.pipe.base.UpstreamFailureNoWorkFound, f"Skipping reprocessing of detector 44 because:\n{msg}" 

562 ): 

563 lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum) 

564 

565 def test_runQuantum_no_sky_corr(self): 

566 """Test that the task will run if using the sky_corr input is 

567 diabled. 

568 """ 

569 config = ReprocessVisitImageTask.ConfigClass() 

570 config.do_use_sky_corr = False 

571 task = ReprocessVisitImageTask(config=config) 

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

573 

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

575 task, 

576 self.butler, 

577 self.visit_id, 

578 { 

579 "exposures": [self.exposure0_id], 

580 "preliminary_mask": self.visit_id, 

581 "visit_summary": self.visit_only_id, 

582 "initial_photo_calib": self.visit_id, 

583 "background_1": self.visit_id, 

584 "calib_sources": self.visit_only_id, 

585 "background_to_photometric_ratio": self.visit_id, 

586 # outputs 

587 "exposure": self.visit_id, 

588 "sources": self.visit_id, 

589 "sources_footprints": self.visit_id, 

590 "background": self.visit_id, 

591 }, 

592 ) 

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

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

595 self.assertEqual( 

596 mock_run.call_args.kwargs.keys(), 

597 { 

598 "exposures", 

599 "preliminary_mask", 

600 "initial_photo_calib", 

601 "psf", 

602 "background", 

603 "ap_corr", 

604 "photo_calib", 

605 "wcs", 

606 "calib_sources", 

607 "id_generator", 

608 "background_to_photometric_ratio", 

609 "result", 

610 }, 

611 ) 

612 

613 def test_runQuantum_illumination_correction(self): 

614 """Test the task with illumination correction enabled.""" 

615 config = ReprocessVisitImageTask.ConfigClass() 

616 config.do_apply_flat_background_ratio = True 

617 task = ReprocessVisitImageTask(config=config) 

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

619 

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

621 task, 

622 self.butler, 

623 self.visit_id, 

624 { 

625 "exposures": [self.exposure0_id], 

626 "preliminary_mask": self.visit_id, 

627 "visit_summary": self.visit_only_id, 

628 "initial_photo_calib": self.visit_id, 

629 "background_1": self.visit_id, 

630 "background_2": self.visit_id, 

631 "calib_sources": self.visit_only_id, 

632 "background_to_photometric_ratio": self.visit_id, 

633 # outputs 

634 "exposure": self.visit_id, 

635 "sources": self.visit_id, 

636 "sources_footprints": self.visit_id, 

637 "background": self.visit_id, 

638 }, 

639 ) 

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

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

642 self.assertEqual( 

643 mock_run.call_args.kwargs.keys(), 

644 { 

645 "exposures", 

646 "preliminary_mask", 

647 "initial_photo_calib", 

648 "psf", 

649 "background", 

650 "ap_corr", 

651 "photo_calib", 

652 "wcs", 

653 "calib_sources", 

654 "id_generator", 

655 "background_to_photometric_ratio", 

656 "result", 

657 }, 

658 ) 

659 

660 

661def setup_module(module): 

662 lsst.utils.tests.init() 

663 

664 

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

666 pass 

667 

668 

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

670 lsst.utils.tests.init() 

671 unittest.main()