Skip to content

Commit b7fd09b

Browse files
committed
Fix mangled optional_outputs handling
I wasn't being consistent about how I was handling optional_outputs; this makes it treat both a list and None correctly, and tests that things get pruned and/or not included in the output struct.
1 parent 6111f3d commit b7fd09b

File tree

2 files changed

+57
-28
lines changed

2 files changed

+57
-28
lines changed

python/lsst/pipe/tasks/calibrateImage.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,20 @@ class CalibrateImageConnections(pipeBase.PipelineTaskConnections,
139139

140140
def __init__(self, *, config=None):
141141
super().__init__(config=config)
142-
if not config.optional_outputs:
142+
if config.optional_outputs is None or "psf_stars" not in config.optional_outputs:
143143
del self.psf_stars
144+
if config.optional_outputs is None or "psf_stars_footprints" not in config.optional_outputs:
144145
del self.psf_stars_footprints
146+
if config.optional_outputs is None or "astrometry_matches" not in config.optional_outputs:
145147
del self.astrometry_matches
148+
if config.optional_outputs is None or "photometry_matches" not in config.optional_outputs:
146149
del self.photometry_matches
147150

148151

149152
class CalibrateImageConfig(pipeBase.PipelineTaskConfig, pipelineConnections=CalibrateImageConnections):
150153
optional_outputs = pexConfig.ListField(
151-
doc="Which optional outputs to save (as their connection name)?",
154+
doc="Which optional outputs to save (as their connection name)?"
155+
" If None, do not output any of these datasets.",
152156
dtype=str,
153157
# TODO: note somewhere to disable this for benchmarking, but should
154158
# we always have it on for production runs?
@@ -526,7 +530,7 @@ def run(self, *, exposures, id_generator=None, result=None):
526530
result.stars = result.stars_footprints.asAstropy()
527531

528532
astrometry_matches, astrometry_meta = self._fit_astrometry(result.exposure, result.stars_footprints)
529-
if self.config.optional_outputs:
533+
if self.config.optional_outputs is not None and "astrometry_matches" in self.config.optional_outputs:
530534
result.astrometry_matches = lsst.meas.astrom.denormalizeMatches(astrometry_matches,
531535
astrometry_meta)
532536

@@ -535,7 +539,7 @@ def run(self, *, exposures, id_generator=None, result=None):
535539
result.stars_footprints)
536540
# fit_photometry returns a new catalog, so we need a new astropy table view.
537541
result.stars = result.stars_footprints.asAstropy()
538-
if self.config.optional_outputs:
542+
if self.config.optional_outputs is not None and "photometry_matches" in self.config.optional_outputs:
539543
result.photometry_matches = lsst.meas.astrom.denormalizeMatches(photometry_matches,
540544
photometry_meta)
541545

tests/test_calibrateImage.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,22 @@ def test_run_2_snaps(self):
207207

208208
self._check_run(calibrate, result)
209209

210+
def test_run_no_optionals(self):
211+
"""Test that disabling optional outputs removes them from the output
212+
struct, as appropriate.
213+
"""
214+
self.config.optional_outputs = None
215+
calibrate = CalibrateImageTask(config=self.config)
216+
calibrate.astrometry.setRefObjLoader(self.ref_loader)
217+
calibrate.photometry.match.setRefObjLoader(self.ref_loader)
218+
result = calibrate.run(exposures=self.exposure)
219+
220+
self._check_run(calibrate, result)
221+
# These are the only optional outputs that require extra computation,
222+
# the others are included in the output struct regardless.
223+
self.assertNotIn("astrometry_matches", result.getDict())
224+
self.assertNotIn("photometry_matches", result.getDict())
225+
210226
def test_handle_snaps(self):
211227
calibrate = CalibrateImageTask(config=self.config)
212228
self.assertEqual(calibrate._handle_snaps(self.exposure), self.exposure)
@@ -543,30 +559,39 @@ def test_runQuantum_2_snaps(self):
543559
self.assertEqual(mock_run.call_args.kwargs.keys(), {"exposures", "result", "id_generator"})
544560

545561
def test_runQuantum_no_optional_outputs(self):
546-
config = CalibrateImageTask.ConfigClass()
547-
config.optional_outputs = None
548-
task = CalibrateImageTask(config=config)
549-
lsst.pipe.base.testUtils.assertValidInitOutput(task)
550-
551-
quantum = lsst.pipe.base.testUtils.makeQuantum(
552-
task, self.butler, self.visit_id,
553-
{"exposures": [self.exposure0_id],
554-
"astrometry_ref_cat": [self.htm_id],
555-
"photometry_ref_cat": [self.htm_id],
556-
# outputs
557-
"exposure": self.visit_id,
558-
"stars": self.visit_id,
559-
"stars_footprints": self.visit_id,
560-
"applied_photo_calib": self.visit_id,
561-
"background": self.visit_id,
562-
})
563-
mock_run = lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum)
564-
565-
# Ensure the reference loaders have been configured.
566-
self.assertEqual(task.astrometry.refObjLoader.name, "gaia_dr3_20230707")
567-
self.assertEqual(task.photometry.match.refObjLoader.name, "ps1_pv3_3pi_20170110")
568-
# Check that the proper kwargs are passed to run().
569-
self.assertEqual(mock_run.call_args.kwargs.keys(), {"exposures", "result", "id_generator"})
562+
# All the possible connections: we modify this to test each one by
563+
# popping off the removed connection, then re-setting it.
564+
connections = {"exposures": [self.exposure0_id, self.exposure1_id],
565+
"astrometry_ref_cat": [self.htm_id],
566+
"photometry_ref_cat": [self.htm_id],
567+
# outputs
568+
"exposure": self.visit_id,
569+
"stars": self.visit_id,
570+
"stars_footprints": self.visit_id,
571+
"background": self.visit_id,
572+
"psf_stars": self.visit_id,
573+
"psf_stars_footprints": self.visit_id,
574+
"applied_photo_calib": self.visit_id,
575+
"initial_pvi_background": self.visit_id,
576+
"astrometry_matches": self.visit_id,
577+
"photometry_matches": self.visit_id,
578+
}
579+
580+
# Check that we can turn off one output at a time.
581+
for optional in ["psf_stars", "psf_stars_footprints", "astrometry_matches", "photometry_matches"]:
582+
config = CalibrateImageTask.ConfigClass()
583+
config.optional_outputs.remove(optional)
584+
task = CalibrateImageTask(config=config)
585+
lsst.pipe.base.testUtils.assertValidInitOutput(task)
586+
# Save the removed one for the next test.
587+
temp = connections.pop(optional)
588+
# This will fail with "Error in connection ..." if we don't pop
589+
# the optional item from the connections list just above.
590+
quantum = lsst.pipe.base.testUtils.makeQuantum(task, self.butler, self.visit_id, connections)
591+
# This confirms that the outputs did skip the removed one.
592+
self.assertNotIn(optional, quantum.outputs)
593+
# Restore the one we removed for the next test.
594+
connections[optional] = temp
570595

571596
def test_lintConnections(self):
572597
"""Check that the connections are self-consistent.

0 commit comments

Comments
 (0)