Skip to content
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-45042: ComputeExposureSummaryStats needs to be able to turn off "update" code #977

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions python/lsst/pipe/tasks/calibrateImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,11 +408,6 @@ def setDefaults(self):
self.photometry.match.sourceSelection.doRequirePrimary = False
self.photometry.match.sourceSelection.doUnresolved = False

# All sources should be good for PSF summary statistics.
# TODO: These should both be changed to calib_psf_used with DM-41640.
self.compute_summary_stats.starSelection = "calib_photometry_used"
self.compute_summary_stats.starSelector.flags.good = ["calib_photometry_used"]

def validate(self):
super().validate()

Expand Down
189 changes: 135 additions & 54 deletions python/lsst/pipe/tasks/computeExposureSummaryStats.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,55 @@

class ComputeExposureSummaryStatsConfig(pexConfig.Config):
"""Config for ComputeExposureSummaryTask"""
doUpdatePsfModelStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the grid-based PSF model maximum - minimum range metrics (psfTraceRadiusDelta & "
"psfApFluxDelta)? Set to False if speed is of the essence.",
)
doUpdateApCorrModelStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the grid-based apCorr model maximum - minimum range metric (psfApCorrSigmaScaledDelta)? "
"Set to False if speed is of the essence.",
)
doUpdateMaxDistToNearestPsfStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the grid-based maximum distance to the nearest PSF star PSF model metric "
"(maxDistToNearestPsf)? Set to False if speed is of the essence.",
)
doUpdateWcsStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the wcs statistics? Set to False if speed is of the essence.",
)
doUpdatePhotoCalibStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the photoCalib statistics? Set to False if speed is of the essence.",
)
doUpdateBackgroundStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the background statistics? Set to False if speed is of the essence.",
)
doUpdateMaskedImageStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the masked image (i.e. skyNoise & meanVar) statistics? Set to False "
"if speed is of the essence.",
)
doUpdateMagnitudeLimitStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the magnitude limit depth statistics? Set to False if speed is of the essence.",
)
doUpdateEffectiveTimeStats = pexConfig.Field(
dtype=bool,
default=True,
doc="Update the effective time statistics? Set to False if speed is of the essence.",
)
sigmaClip = pexConfig.Field(
dtype=float,
doc="Sigma for outlier rejection for sky noise.",
Expand Down Expand Up @@ -164,13 +213,26 @@ class ComputeExposureSummaryStatsTask(pipeBase.Task):
"""Task to compute exposure summary statistics.

This task computes various quantities suitable for DPDD and other
downstream processing at the detector centers, including:
downstream processing at the detector centers. The non-optionally
computed quantities are:
- expTime
- psfSigma
- psfArea
- psfIxx
- psfIyy
- psfIxy

And these quantities which are computed from the stars in the detector:
- psfStarDeltaE1Median
- psfStarDeltaE2Median
- psfStarDeltaE1Scatter
- psfStarDeltaE2Scatter
- psfStarDeltaSizeMedian
- psfStarDeltaSizeScatter
- psfStarScaledDeltaSizeScatter

The subsequently listed quatities are optionally computed via the
"doUpdateX" config parameters (which all default to True):
- ra
- dec
- pixelScale (arcsec/pixel)
Expand All @@ -184,15 +246,6 @@ class ComputeExposureSummaryStatsTask(pipeBase.Task):
- astromOffsetMean
- astromOffsetStd

These additional quantities are computed from the stars in the detector:
- psfStarDeltaE1Median
- psfStarDeltaE2Median
- psfStarDeltaE1Scatter
- psfStarDeltaE2Scatter
- psfStarDeltaSizeMedian
- psfStarDeltaSizeScatter
- psfStarScaledDeltaSizeScatter

These quantities are computed based on the PSF model and image mask
to assess the robustness of the PSF model across a given detector
(against, e.g., extrapolation instability):
Expand Down Expand Up @@ -250,20 +303,26 @@ def run(self, exposure, sources, background):
summary, psf, bbox, sources, image_mask=exposure.mask, image_ap_corr_map=exposure.apCorrMap
)

wcs = exposure.getWcs()
visitInfo = exposure.getInfo().getVisitInfo()
self.update_wcs_stats(summary, wcs, bbox, visitInfo)
if self.config.doUpdateWcsStats:
wcs = exposure.getWcs()
visitInfo = exposure.getInfo().getVisitInfo()
self.update_wcs_stats(summary, wcs, bbox, visitInfo)

photoCalib = exposure.getPhotoCalib()
self.update_photo_calib_stats(summary, photoCalib)
if self.config.doUpdatePhotoCalibStats:
photoCalib = exposure.getPhotoCalib()
self.update_photo_calib_stats(summary, photoCalib)

self.update_background_stats(summary, background)
if self.config.doUpdateBackgroundStats:
self.update_background_stats(summary, background)

self.update_masked_image_stats(summary, exposure.getMaskedImage())
if self.config.doUpdateMaskedImageStats:
self.update_masked_image_stats(summary, exposure.getMaskedImage())

self.update_magnitude_limit_stats(summary, exposure)
if self.config.doUpdateMagnitudeLimitStats:
self.update_magnitude_limit_stats(summary, exposure)

self.update_effective_time_stats(summary, exposure)
if self.config.doUpdateEffectiveTimeStats:
self.update_effective_time_stats(summary, exposure)

md = exposure.getMetadata()
if 'SFM_ASTROM_OFFSET_MEAN' in md:
Expand Down Expand Up @@ -340,33 +399,49 @@ def update_psf_stats(
# 750bffe6620e565bda731add1509507f5c40c8bb/src/PsfFlux.cc#L112
summary.psfArea = float(np.sum(im.array)/np.sum(im.array**2.))

if image_mask is not None:
psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
self.log.debug("Using radius of %.3f (pixels) for psfApFluxDelta metric", psfApRadius)
psfTraceRadiusDelta, psfApFluxDelta = compute_psf_image_deltas(
image_mask,
psf,
sampling=self.config.psfGridSampling,
ap_radius_pix=psfApRadius,
bad_mask_bits=self.config.psfBadMaskPlanes
)
summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
summary.psfApFluxDelta = float(psfApFluxDelta)
if image_ap_corr_map is not None:
if self.config.psfApCorrFieldName not in image_ap_corr_map.keys():
self.log.warn(f"{self.config.psfApCorrFieldName} not found in "
"image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
psfApCorrSigmaScaledDelta = nan
else:
image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
psfApCorrSigmaScaledDelta = compute_ap_corr_sigma_scaled_delta(
image_mask,
image_ap_corr_field,
summary.psfSigma,
sampling=self.config.psfGridSampling,
bad_mask_bits=self.config.psfBadMaskPlanes,
)
summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
if not self.config.doUpdatePsfModelStats:
self.log.info("Note: not computing grid-based PSF model maximum - minimum range metrics "
"psfTraceRadiusDelta & psfApFluxDelta.")
else:
if image_mask is None:
self.log.info("Note: computation of grid-based PSF model maximum - minimum range metrics "
"was requested, but required image_mask parameter was not provided.")
else:
psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
self.log.debug("Using radius of %.3f (pixels) for psfApFluxDelta metric.", psfApRadius)

psfTraceRadiusDelta, psfApFluxDelta = compute_psf_image_deltas(
image_mask,
psf,
sampling=self.config.psfGridSampling,
ap_radius_pix=psfApRadius,
bad_mask_bits=self.config.psfBadMaskPlanes
)
summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
summary.psfApFluxDelta = float(psfApFluxDelta)
if not self.config.doUpdateApCorrModelStats:
self.log.info("Note: not computing grid-based apCorr model maximum - minimum range metric "
"psfApCorrSigmaScaledDelta.")
else:
if image_mask is None:
self.log.info("Note: computation of grid-based apCorr model maximum - minimum metric "
"was requested, but required image_mask parameter was not provided.")
else:
if image_ap_corr_map is not None:
if self.config.psfApCorrFieldName not in image_ap_corr_map.keys():
self.log.warn(f"{self.config.psfApCorrFieldName} not found in "
"image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
psfApCorrSigmaScaledDelta = nan
else:
image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
psfApCorrSigmaScaledDelta = compute_ap_corr_sigma_scaled_delta(
image_mask,
image_ap_corr_field,
summary.psfSigma,
sampling=self.config.psfGridSampling,
bad_mask_bits=self.config.psfBadMaskPlanes,
)
summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)

if sources is None:
# No sources are available (as in some tests and rare cases where
Expand Down Expand Up @@ -427,14 +502,20 @@ def update_psf_stats(
summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)

if image_mask is not None:
maxDistToNearestPsf = maximum_nearest_psf_distance(
image_mask,
psf_cat,
sampling=self.config.psfSampling,
bad_mask_bits=self.config.psfBadMaskPlanes
)
summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
if not self.config.doUpdateMaxDistToNearestPsfStats:
self.log.info("Note: not computing grid-based maxDistToNearestPsf PSF model metric.")
else:
if image_mask is None:
self.log.info("Note: computation of maxDistToNearestPsf PSF model metric was "
"requested, but required image_mask parameter was not provided.")
else:
maxDistToNearestPsf = maximum_nearest_psf_distance(
image_mask,
psf_cat,
sampling=self.config.psfSampling,
bad_mask_bits=self.config.psfBadMaskPlanes
)
summary.maxDistToNearestPsf = float(maxDistToNearestPsf)

def update_wcs_stats(self, summary, wcs, bbox, visitInfo):
"""Compute all summary-statistic fields that depend on the WCS model.
Expand Down
46 changes: 45 additions & 1 deletion tests/test_computeExposureSummaryStats.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,56 @@ def testComputeExposureSummary(self):
background.append(backobj)

# Configure and run the task
expSummaryTaskNoUpdates = ComputeExposureSummaryStatsTask()
expSummaryTask = ComputeExposureSummaryStatsTask()
# Configure nominal values for effective time calculation (normalized to 1s exposure)
expSummaryTask.config.fiducialZeroPoint = {band: float(zp - 2.5*np.log10(expTime))}
expSummaryTask.config.fiducialPsfSigma = {band: float(psfSize)}
expSummaryTask.config.fiducialSkyBackground = {band: float(skyMean/expTime)}
# Run the task
# Run the task with optianal updates turned off
expSummaryTaskNoUpdates.config.doUpdatePsfModelStats = False
expSummaryTaskNoUpdates.config.doUpdateApCorrModelStats = False
expSummaryTaskNoUpdates.config.doUpdateMaxDistToNearestPsfStats = False
expSummaryTaskNoUpdates.config.doUpdateWcsStats = False
expSummaryTaskNoUpdates.config.doUpdatePhotoCalibStats = False
expSummaryTaskNoUpdates.config.doUpdateBackgroundStats = False
expSummaryTaskNoUpdates.config.doUpdateMaskedImageStats = False
expSummaryTaskNoUpdates.config.doUpdateMagnitudeLimitStats = False
expSummaryTaskNoUpdates.config.doUpdateEffectiveTimeStats = False

summary = expSummaryTaskNoUpdates.run(exposure, None, background)
# Test the outputs
self.assertTrue(np.isnan(summary.ra))
self.assertTrue(np.isnan(summary.dec))

# The following PSF metrics are always updated
self.assertFloatsAlmostEqual(summary.expTime, expTime)
self.assertFloatsAlmostEqual(summary.psfSigma, psfSize)
self.assertFloatsAlmostEqual(summary.psfIxx, psfSize**2.)
self.assertFloatsAlmostEqual(summary.psfIyy, psfSize**2.)
self.assertFloatsAlmostEqual(summary.psfIxy, 0.0)
self.assertFloatsAlmostEqual(summary.psfArea, 23.088975164455444)

# The following should not have been updated (i.e. set to nan)
self.assertTrue(np.isnan(summary.psfTraceRadiusDelta))
self.assertTrue(np.isnan(summary.psfApFluxDelta))
self.assertTrue(np.isnan(summary.psfApCorrSigmaScaledDelta))
self.assertTrue(np.isnan(summary.maxDistToNearestPsf))
self.assertTrue(np.isnan(summary.pixelScale))

self.assertTrue(np.isnan(summary.zenithDistance))
self.assertTrue(np.isnan(summary.skyBg))
self.assertTrue(np.isnan(summary.skyNoise))
self.assertTrue(np.isnan(summary.meanVar))
self.assertTrue(np.isnan(summary.zeroPoint))

self.assertTrue(np.isnan(summary.effTime))
self.assertTrue(np.isnan(summary.effTimePsfSigmaScale))
self.assertTrue(np.isnan(summary.effTimeSkyBgScale))
self.assertTrue(np.isnan(summary.effTimeZeroPointScale))
self.assertTrue(np.isnan(summary.magLim))

# Run the task with updates
summary = expSummaryTask.run(exposure, None, background)

# Test the outputs
Expand Down
Loading