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
« 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/>.
22import tempfile
23import unittest
25import astropy.table
26import numpy as np
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
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.")
50 record = summary.addNew()
51 record.setId(detector)
52 record.setApCorrMap(lsst.afw.image.ApCorrMap()),
54 record.setPsf(psf)
55 record.setPhotoCalib(photo_calib)
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)
70 lsst.afw.image.ExposureSummaryStats().update_record(record),
72 return summary
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]))
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)
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
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())
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 )
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
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
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)
194 def test_run(self):
196 background_to_photometric_ratio_value = 1.1
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
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
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 )
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)
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 )
245 if not do_apply_flat_background_ratio:
246 result_false = result
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 )
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 )
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())
266 self.assertEqual(result.exposure.metadata["BUNIT"], "nJy")
268 def test_update_masks(self):
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
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)
320class ReprocessVisitImageTaskRunQuantumTests(lsst.utils.tests.TestCase):
321 """Tests of ``ReprocessVisitImageTask.runQuantum``, which need a test
322 butler, but do not need real data.
323 """
325 def setUp(self):
326 instrument = "testCam"
327 exposure0 = 101
328 exposure1 = 102
329 visit = 100101
330 detector = 42
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)
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)
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)
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 )
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 )
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})
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)
443 def tearDown(self):
444 self.repo_path.cleanup()
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)
451 def test_runQuantum(self):
452 task = ReprocessVisitImageTask()
453 lsst.pipe.base.testUtils.assertValidInitOutput(task)
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 )
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)
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)
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)
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)
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)
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 )
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)
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 )
661def setup_module(module):
662 lsst.utils.tests.init()
665class MemoryTestCase(lsst.utils.tests.MemoryTestCase):
666 pass
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()