Skip to content

Commit f235c25

Browse files
committed
Make summary stats updates configurably optional
Computation of these statistics can be time consuming, so this adds an option to turn any of them off for scenarios where they are not useful (which they would be for, e.g., downstream processing decision making) and/or processing time minimization is of the essence.
1 parent 3dd589e commit f235c25

File tree

1 file changed

+128
-53
lines changed

1 file changed

+128
-53
lines changed

python/lsst/pipe/tasks/computeExposureSummaryStats.py

+128-53
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,50 @@
4040

4141
class ComputeExposureSummaryStatsConfig(pexConfig.Config):
4242
"""Config for ComputeExposureSummaryTask"""
43+
doUpdatePsfModelStats = pexConfig.Field(
44+
dtype=bool,
45+
default=True,
46+
doc="Update the grid-based PSF model fidelity statistics (psfTraceRadiusDelta & psfApFluxDelta)?"
47+
"Set to False if speed is of the essence.",
48+
)
49+
doUpdateApCorrModelStats = pexConfig.Field(
50+
dtype=bool,
51+
default=True,
52+
doc="Update the grid-based apCorr model fidelity statistic (psfApCorrSigmaScaledDelta)? "
53+
"Set to False if speed is of the essence.",
54+
)
55+
doUpdateMaxDistToNearestPsfStats = pexConfig.Field(
56+
dtype=bool,
57+
default=True,
58+
doc="Update the grid-based maximun distance to the nearest PSF star fidelity statistic "
59+
"(maxDistToNearestPsf)? Set to False if speed is of the essence.",
60+
)
61+
doUpdateWcsStats = pexConfig.Field(
62+
dtype=bool,
63+
default=True,
64+
doc="Update the wcs statistics? Set to False if speed is of the essence.",
65+
)
66+
doUpdatePhotoCalibStats = pexConfig.Field(
67+
dtype=bool,
68+
default=True,
69+
doc="Update the photoCalib statistics? Set to False if speed is of the essence.",
70+
)
71+
doUpdateBackgroundStats = pexConfig.Field(
72+
dtype=bool,
73+
default=True,
74+
doc="Update the background statistics? Set to False if speed is of the essence.",
75+
)
76+
doUpdateMaskedImageStats = pexConfig.Field(
77+
dtype=bool,
78+
default=True,
79+
doc="Update the masked image (i.e. skyNoise & meanVar) statistics? Set to False "
80+
"if speed is of the essence.",
81+
)
82+
doUpdateEffectiveTimeStats = pexConfig.Field(
83+
dtype=bool,
84+
default=True,
85+
doc="Update the effective time statistics? Set to False if speed is of the essence.",
86+
)
4387
sigmaClip = pexConfig.Field(
4488
dtype=float,
4589
doc="Sigma for outlier rejection for sky noise.",
@@ -158,13 +202,26 @@ class ComputeExposureSummaryStatsTask(pipeBase.Task):
158202
"""Task to compute exposure summary statistics.
159203
160204
This task computes various quantities suitable for DPDD and other
161-
downstream processing at the detector centers, including:
205+
downstream processing at the detector centers. The non-optionally
206+
computed quantities are:
162207
- expTime
163208
- psfSigma
164209
- psfArea
165210
- psfIxx
166211
- psfIyy
167212
- psfIxy
213+
214+
And these quantities which are computed from the stars in the detector:
215+
- psfStarDeltaE1Median
216+
- psfStarDeltaE2Median
217+
- psfStarDeltaE1Scatter
218+
- psfStarDeltaE2Scatter
219+
- psfStarDeltaSizeMedian
220+
- psfStarDeltaSizeScatter
221+
- psfStarScaledDeltaSizeScatter
222+
223+
The subsequently listed quatities are optionally computed via the
224+
"doUpdateX" config parameters (which all default to True):
168225
- ra
169226
- dec
170227
- pixelScale (arcsec/pixel)
@@ -178,15 +235,6 @@ class ComputeExposureSummaryStatsTask(pipeBase.Task):
178235
- astromOffsetMean
179236
- astromOffsetStd
180237
181-
These additional quantities are computed from the stars in the detector:
182-
- psfStarDeltaE1Median
183-
- psfStarDeltaE2Median
184-
- psfStarDeltaE1Scatter
185-
- psfStarDeltaE2Scatter
186-
- psfStarDeltaSizeMedian
187-
- psfStarDeltaSizeScatter
188-
- psfStarScaledDeltaSizeScatter
189-
190238
These quantities are computed based on the PSF model and image mask
191239
to assess the robustness of the PSF model across a given detector
192240
(against, e.g., extrapolation instability):
@@ -243,18 +291,23 @@ def run(self, exposure, sources, background):
243291
summary, psf, bbox, sources, image_mask=exposure.mask, image_ap_corr_map=exposure.apCorrMap
244292
)
245293

246-
wcs = exposure.getWcs()
247-
visitInfo = exposure.getInfo().getVisitInfo()
248-
self.update_wcs_stats(summary, wcs, bbox, visitInfo)
294+
if self.config.doUpdateWcsStats:
295+
wcs = exposure.getWcs()
296+
visitInfo = exposure.getInfo().getVisitInfo()
297+
self.update_wcs_stats(summary, wcs, bbox, visitInfo)
249298

250-
photoCalib = exposure.getPhotoCalib()
251-
self.update_photo_calib_stats(summary, photoCalib)
299+
if self.config.doUpdatePhotoCalibStats:
300+
photoCalib = exposure.getPhotoCalib()
301+
self.update_photo_calib_stats(summary, photoCalib)
252302

253-
self.update_background_stats(summary, background)
303+
if self.config.doUpdateBackgroundStats:
304+
self.update_background_stats(summary, background)
254305

255-
self.update_masked_image_stats(summary, exposure.getMaskedImage())
306+
if self.config.doUpdateMaskedImageStats:
307+
self.update_masked_image_stats(summary, exposure.getMaskedImage())
256308

257-
self.update_effective_time_stats(summary, exposure)
309+
if self.config.doUpdateEffectiveTimeStats:
310+
self.update_effective_time_stats(summary, exposure)
258311

259312
md = exposure.getMetadata()
260313
if 'SFM_ASTROM_OFFSET_MEAN' in md:
@@ -331,33 +384,49 @@ def update_psf_stats(
331384
# 750bffe6620e565bda731add1509507f5c40c8bb/src/PsfFlux.cc#L112
332385
summary.psfArea = float(np.sum(im.array)/np.sum(im.array**2.))
333386

334-
if image_mask is not None:
335-
psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
336-
self.log.debug("Using radius of %.3f (pixels) for psfApFluxDelta metric", psfApRadius)
337-
psfTraceRadiusDelta, psfApFluxDelta = compute_psf_image_deltas(
338-
image_mask,
339-
psf,
340-
sampling=self.config.psfGridSampling,
341-
ap_radius_pix=psfApRadius,
342-
bad_mask_bits=self.config.psfBadMaskPlanes
343-
)
344-
summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
345-
summary.psfApFluxDelta = float(psfApFluxDelta)
346-
if image_ap_corr_map is not None:
347-
if self.config.psfApCorrFieldName not in image_ap_corr_map.keys():
348-
self.log.warn(f"{self.config.psfApCorrFieldName} not found in "
349-
"image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
350-
psfApCorrSigmaScaledDelta = nan
351-
else:
352-
image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
353-
psfApCorrSigmaScaledDelta = compute_ap_corr_sigma_scaled_delta(
354-
image_mask,
355-
image_ap_corr_field,
356-
summary.psfSigma,
357-
sampling=self.config.psfGridSampling,
358-
bad_mask_bits=self.config.psfBadMaskPlanes,
359-
)
360-
summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
387+
if not self.config.doUpdatePsfModelStats:
388+
self.log.info("Note: not computing grid-based PSF model fidelity metrics "
389+
"psfTraceRadiusDelta & psfApFluxDelta.")
390+
else:
391+
if image_mask is None:
392+
self.log.info("Note: computation of grid-based PSF model fidelity metrics was requested, "
393+
"but required image_mask parameter was not provided.")
394+
else:
395+
psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
396+
self.log.debug("Using radius of %.3f (pixels) for psfApFluxDelta metric.", psfApRadius)
397+
398+
psfTraceRadiusDelta, psfApFluxDelta = compute_psf_image_deltas(
399+
image_mask,
400+
psf,
401+
sampling=self.config.psfGridSampling,
402+
ap_radius_pix=psfApRadius,
403+
bad_mask_bits=self.config.psfBadMaskPlanes
404+
)
405+
summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
406+
summary.psfApFluxDelta = float(psfApFluxDelta)
407+
if not self.config.doUpdateApCorrModelStats:
408+
self.log.info("Note: not computing grid-based apCorr model fidelity metric "
409+
"psfApCorrSigmaScaledDelta.")
410+
else:
411+
if image_mask is None:
412+
self.log.info("Note: computation of grid-based apCorr model fidelity metric was requested, "
413+
"but required image_mask parameter was not provided.")
414+
else:
415+
if image_ap_corr_map is not None:
416+
if self.config.psfApCorrFieldName not in image_ap_corr_map.keys():
417+
self.log.warn(f"{self.config.psfApCorrFieldName} not found in "
418+
"image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
419+
psfApCorrSigmaScaledDelta = nan
420+
else:
421+
image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
422+
psfApCorrSigmaScaledDelta = compute_ap_corr_sigma_scaled_delta(
423+
image_mask,
424+
image_ap_corr_field,
425+
summary.psfSigma,
426+
sampling=self.config.psfGridSampling,
427+
bad_mask_bits=self.config.psfBadMaskPlanes,
428+
)
429+
summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
361430

362431
if sources is None:
363432
# No sources are available (as in some tests and rare cases where
@@ -418,14 +487,20 @@ def update_psf_stats(
418487
summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
419488
summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
420489

421-
if image_mask is not None:
422-
maxDistToNearestPsf = maximum_nearest_psf_distance(
423-
image_mask,
424-
psf_cat,
425-
sampling=self.config.psfSampling,
426-
bad_mask_bits=self.config.psfBadMaskPlanes
427-
)
428-
summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
490+
if not self.config.doUpdateMaxDistToNearestPsfStats:
491+
self.log.info("Note: not computing grid-based maxDistToNearestPsf model fidelity metric.")
492+
else:
493+
if image_mask is None:
494+
self.log.info("Note: computation of maxDistToNearestPsf PSF model fidelity metric was "
495+
"requested, but required image_mask parameter was not provided.")
496+
else:
497+
maxDistToNearestPsf = maximum_nearest_psf_distance(
498+
image_mask,
499+
psf_cat,
500+
sampling=self.config.psfSampling,
501+
bad_mask_bits=self.config.psfBadMaskPlanes
502+
)
503+
summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
429504

430505
def update_wcs_stats(self, summary, wcs, bbox, visitInfo):
431506
"""Compute all summary-statistic fields that depend on the WCS model.

0 commit comments

Comments
 (0)