-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DM-39842: Configurable exception handling in CalibrateImageTask #835
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,9 +76,9 @@ class CalibrateImageConnections(pipeBase.PipelineTaskConnections, | |
storageClass="SourceCatalog", | ||
) | ||
|
||
# TODO: We want some kind of flag on Exposures/Catalogs to make it obvious | ||
# which components had failed to be computed/persisted | ||
output_exposure = connectionTypes.Output( | ||
# TODO DM-38732: We want some kind of flag on Exposures/Catalogs to make | ||
# it obvious which components had failed to be computed/persisted. | ||
exposure = connectionTypes.Output( | ||
doc="Photometrically calibrated exposure with fitted calibrations and summary statistics.", | ||
name="initial_pvi", | ||
storageClass="ExposureF", | ||
|
@@ -408,6 +408,8 @@ def __init__(self, initial_stars_schema=None, **kwargs): | |
|
||
def runQuantum(self, butlerQC, inputRefs, outputRefs): | ||
inputs = butlerQC.get(inputRefs) | ||
exposures = inputs.pop("exposures") | ||
|
||
id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId) | ||
|
||
astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader( | ||
|
@@ -424,12 +426,33 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs): | |
config=self.config.photometry_ref_loader, log=self.log) | ||
self.photometry.match.setRefObjLoader(photometry_loader) | ||
|
||
outputs = self.run(id_generator=id_generator, **inputs) | ||
|
||
butlerQC.put(outputs, outputRefs) | ||
# This should not happen with a properly configured execution context. | ||
assert not inputs, "runQuantum got more inputs than expected" | ||
|
||
# Specify the fields that `annotate` needs below, to ensure they | ||
# exist, even as None. | ||
result = pipeBase.Struct(exposure=None, | ||
stars_footprints=None, | ||
psf_stars_footprints=None, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm torn as to whether it's better to spell out the possible fields here with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To simplify the call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like it could be fragile if we ever change this task to output more things in the result Struct. Is it not possible to construct a list of arguments to feed the error annotation by iterating over all the existing things in the Struct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not without using a whole bunch of |
||
try: | ||
self.run(exposures=exposures, result=result, id_generator=id_generator) | ||
except pipeBase.AlgorithmError as e: | ||
error = pipeBase.AnnotatedPartialOutputsError.annotate( | ||
e, | ||
self, | ||
result.exposure, | ||
result.psf_stars_footprints, | ||
result.stars_footprints, | ||
log=self.log | ||
) | ||
butlerQC.put(result, outputRefs) | ||
raise error from e | ||
|
||
butlerQC.put(result, outputRefs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. We only |
||
|
||
@timeMethod | ||
def run(self, *, exposures, id_generator=None): | ||
def run(self, *, exposures, id_generator=None, result=None): | ||
"""Find stars and perform psf measurement, then do a deeper detection | ||
and measurement and calibrate astrometry and photometry from that. | ||
|
||
|
@@ -442,13 +465,17 @@ def run(self, *, exposures, id_generator=None): | |
before doing further processing. | ||
id_generator : `lsst.meas.base.IdGenerator`, optional | ||
Object that generates source IDs and provides random seeds. | ||
result : `lsst.pipe.base.Struct`, optional | ||
Result struct that is modified to allow saving of partial outputs | ||
for some failure conditions. If the task completes successfully, | ||
this is also returned. | ||
|
||
Returns | ||
------- | ||
result : `lsst.pipe.base.Struct` | ||
Results as a struct with attributes: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the results Struct guaranteed to have all of the documented attributes, now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what you mean? If the task completes, it returns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, but apparently I never added docs for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's what I added:
|
||
|
||
``output_exposure`` | ||
``exposure`` | ||
Calibrated exposure, with pixels in nJy units. | ||
(`lsst.afw.image.Exposure`) | ||
``stars`` | ||
|
@@ -477,40 +504,44 @@ def run(self, *, exposures, id_generator=None): | |
Reference catalog stars matches used in the photometric fit. | ||
(`list` [`lsst.afw.table.ReferenceMatch`] or `lsst.afw.table.BaseCatalog`) | ||
""" | ||
if result is None: | ||
result = pipeBase.Struct() | ||
if id_generator is None: | ||
id_generator = lsst.meas.base.IdGenerator() | ||
|
||
exposure = self._handle_snaps(exposures) | ||
result.exposure = self._handle_snaps(exposures) | ||
|
||
# TODO remove on DM-43083: work around the fact that we don't want | ||
# to run streak detection in this task in production. | ||
exposure.mask.addMaskPlane("STREAK") | ||
result.exposure.mask.addMaskPlane("STREAK") | ||
|
||
psf_stars, background, candidates = self._compute_psf(exposure, id_generator) | ||
result.psf_stars_footprints, result.background, candidates = self._compute_psf(result.exposure, | ||
id_generator) | ||
result.psf_stars = result.psf_stars_footprints.asAstropy() | ||
|
||
self._measure_aperture_correction(exposure, psf_stars) | ||
self._measure_aperture_correction(result.exposure, result.psf_stars) | ||
|
||
stars = self._find_stars(exposure, background, id_generator) | ||
self._match_psf_stars(psf_stars, stars) | ||
result.stars_footprints = self._find_stars(result.exposure, result.background, id_generator) | ||
self._match_psf_stars(result.psf_stars_footprints, result.stars_footprints) | ||
result.stars = result.stars_footprints.asAstropy() | ||
|
||
astrometry_matches, astrometry_meta = self._fit_astrometry(exposure, stars) | ||
stars, photometry_matches, photometry_meta, photo_calib = self._fit_photometry(exposure, stars) | ||
astrometry_matches, astrometry_meta = self._fit_astrometry(result.exposure, result.stars_footprints) | ||
if self.config.optional_outputs: | ||
result.astrometry_matches = lsst.meas.astrom.denormalizeMatches(astrometry_matches, | ||
astrometry_meta) | ||
|
||
result.stars_footprints, photometry_matches, \ | ||
photometry_meta, result.applied_photo_calib = self._fit_photometry(result.exposure, | ||
result.stars_footprints) | ||
# fit_photometry returns a new catalog, so we need a new astropy table view. | ||
result.stars = result.stars_footprints.asAstropy() | ||
if self.config.optional_outputs: | ||
result.photometry_matches = lsst.meas.astrom.denormalizeMatches(photometry_matches, | ||
photometry_meta) | ||
|
||
self._summarize(exposure, stars, background) | ||
self._summarize(result.exposure, result.stars_footprints, result.background) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the Connections here: https://github.com/lsst/pipe_tasks/blob/main/python/lsst/pipe/tasks/calibrateImage.py#L87 Eventually, the goal will be fore the |
||
|
||
if self.config.optional_outputs: | ||
astrometry_matches = lsst.meas.astrom.denormalizeMatches(astrometry_matches, astrometry_meta) | ||
photometry_matches = lsst.meas.astrom.denormalizeMatches(photometry_matches, photometry_meta) | ||
|
||
return pipeBase.Struct(output_exposure=exposure, | ||
stars_footprints=stars, | ||
stars=stars.asAstropy(), | ||
psf_stars_footprints=psf_stars, | ||
psf_stars=psf_stars.asAstropy(), | ||
background=background, | ||
applied_photo_calib=photo_calib, | ||
astrometry_matches=astrometry_matches, | ||
photometry_matches=photometry_matches) | ||
return result | ||
|
||
def _handle_snaps(self, exposure): | ||
"""Combine two snaps into one exposure, or return a single exposure. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If/when this falls over, will the error message specify that the
runQuantum
being talked about is the one fromcalibrateImage
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the assert will give where it happened. This is a "this should absolutely never happen" error, hence the bare assert.