Skip to content

Commit 1ee01d9

Browse files
bazkiaeileeskelvin
authored andcommitted
Scaling psf model before fitting
This is controled by a new config parameter `scalePsfModel` added to `BrightStarCutoutTask` and `BrightStarSubtractTask`.
1 parent c0b447e commit 1ee01d9

File tree

4 files changed

+127
-59
lines changed

4 files changed

+127
-59
lines changed

python/lsst/pipe/tasks/brightStarSubtraction/brightStarCutout.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ class BrightStarCutoutConfig(
180180
},
181181
)
182182

183+
scalePsfModel = Field[bool](
184+
doc="If True, uses a scale factor to bring the PSF model data to the same level of the star data.",
185+
default=True,
186+
)
187+
183188
# PSF Fitting
184189
useExtendedPsf = Field[bool](
185190
doc="Use the extended PSF model to normalize bright star cutouts.",
@@ -348,10 +353,18 @@ def run(
348353
# Fit a scaled PSF and a pedestal to each bright star cutout
349354
psf = WarpedPsf(inputExposure.getPsf(), pixToPolar, warpingControl)
350355
constantPsf = KernelPsf(FixedKernel(psf.computeKernelImage(Point2D(0, 0))))
356+
# TODO: discuss with Lee whether we should warp the psf here as well?
351357
if self.config.useExtendedPsf:
352358
psfImage = extendedPsf # Assumed to be warped, center at [0,0]
353359
else:
354360
psfImage = constantPsf.computeKernelImage(constantPsf.getAveragePosition())
361+
if self.config.scalePsfModel:
362+
psfNeg = psfImage.array < 0
363+
self.modelScale = np.nanmean(stampMI.image.array) / np.nanmean(psfImage.array[~psfNeg])
364+
psfImage.array *= self.modelScale ######## model scale correction ########
365+
else:
366+
self.modelScale = 1
367+
355368
fitPsfResults = {}
356369
if self.config.doFitPsf:
357370
fitPsfResults = self._fitPsf(stampMI, psfImage)
@@ -586,7 +599,8 @@ def _fitPsf(self, stampMI: MaskedImageF, psfImage: ImageD | ImageF) -> dict[str,
586599
psfMaskedPixels.array[:, :] = (stampMI.mask[psfImage.getBBox()].array & badMaskBitMask).astype(bool)
587600
# TODO: This is np.float64, else FITS metadata serialization fails
588601
# Amir: the following tries to find the fraction of the psf flux in the masked area of the psf image.
589-
psfMaskedFluxFrac = np.dot(psfImage.array.flat, psfMaskedPixels.array.flat).astype(np.float64)
602+
psfMaskedFluxFrac = np.dot(psfImage.array.flat, psfMaskedPixels.array.flat).astype(np.float64) / psfImage.array.sum()
603+
# psfMaskedFluxFrac = np.dot(psfImage.array.astype(bool).flat, psfMaskedPixels.array.flat).astype(np.float64) / psfImage.array.astype(bool).sum()
590604
if psfMaskedFluxFrac > self.config.psfMaskedFluxFracThreshold:
591605
return {} # Handle cases where the PSF image is mostly masked
592606

@@ -622,13 +636,14 @@ def _fitPsf(self, stampMI: MaskedImageF, psfImage: ImageD | ImageF) -> dict[str,
622636
return {} # Handle singular matrix errors
623637
if sumSquaredResiduals.size == 0:
624638
return {} # Handle cases where sum of the squared residuals are empty
625-
scale = solutions[0]
639+
# scale = solutions[0]
640+
scale = solutions[0] * self.modelScale ######## model scale correction ########
626641
if scale <= 0:
627642
return {} # Handle cases where the PSF scale fit has failed
628-
scaleErr = np.sqrt(covarianceMatrix[0, 0])
643+
scaleErr = np.sqrt(covarianceMatrix[0, 0]) * self.modelScale ######## model scale correction ########
629644
pedestal = solutions[1]
630645
pedestalErr = np.sqrt(covarianceMatrix[1, 1])
631-
scalePedestalCov = covarianceMatrix[0, 1]
646+
scalePedestalCov = covarianceMatrix[0, 1] * self.modelScale ######## model scale correction ########
632647
xGradient = solutions[3]
633648
yGradient = solutions[2]
634649

@@ -641,6 +656,7 @@ def _fitPsf(self, stampMI: MaskedImageF, psfImage: ImageD | ImageF) -> dict[str,
641656
psfBBoxGoodSpans = goodSpans.clippedTo(psfImage.getBBox())
642657
psfBBoxGoodSpansX, psfBBoxGoodSpansY = psfBBoxGoodSpans.indices()
643658
psfBBoxData = psfBBoxGoodSpans.flatten(stampMI.image.array, stampMI.getXY0())
659+
paddedPsfImage.array /= self.modelScale ######## model scale correction ########
644660
psfBBoxModel = (
645661
psfBBoxGoodSpans.flatten(paddedPsfImage.array, stampMI.getXY0()) * scale
646662
+ pedestal
@@ -652,7 +668,6 @@ def _fitPsf(self, stampMI: MaskedImageF, psfImage: ImageD | ImageF) -> dict[str,
652668
psfBBoxChiSquared = np.sum(psfBBoxResiduals)
653669
psfBBoxDegreesOfFreedom = len(psfBBoxData) - 4
654670
psfBBoxReducedChiSquared = psfBBoxChiSquared / psfBBoxDegreesOfFreedom
655-
656671
return dict(
657672
scale=scale,
658673
scaleErr=scaleErr,

python/lsst/pipe/tasks/brightStarSubtraction/brightStarStack.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
__all__ = ["BrightStarStackConnections", "BrightStarStackConfig", "BrightStarStackTask"]
2525

2626
import numpy as np
27-
from lsst.afw.image import ImageF, MaskedImageF
27+
from lsst.afw.image import ImageF
2828
from lsst.afw.math import StatisticsControl, statisticsStack, stringToStatisticsProperty
29-
from lsst.geom import Box2I, Extent2I, Point2I
29+
from lsst.geom import Point2I
3030
from lsst.meas.algorithms import BrightStarStamps
3131
from lsst.pex.config import Field, ListField
3232
from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections, Struct
@@ -66,7 +66,7 @@ class BrightStarStackConfig(
6666

6767
subsetStampNumber = Field[int](
6868
doc="Number of stamps per subset to generate stacked images for.",
69-
default=20,
69+
default=2,
7070
)
7171
globalReducedChiSquaredThreshold = Field[float](
7272
doc="Threshold for global reduced chi-squared for bright star stamps.",
@@ -172,9 +172,14 @@ def run(
172172

173173
subsetStampMIs = []
174174
tempStampMIs = []
175+
all_stars = 0
176+
used_stars = 0
175177
for stampsDDH in brightStarStamps:
176178
stamps = stampsDDH.get()
179+
all_stars += len(stamps)
177180
for stamp in stamps:
181+
# print("globalReducedChiSquared: stamp ", stamp.globalReducedChiSquared, "config ", self.config.globalReducedChiSquaredThreshold)
182+
# print("psfReducedChiSquared: stamp ", stamp.psfReducedChiSquared, "config ", self.config.psfReducedChiSquaredThreshold)
178183
if (
179184
stamp.globalReducedChiSquared > self.config.globalReducedChiSquaredThreshold
180185
or stamp.psfReducedChiSquared > self.config.psfReducedChiSquaredThreshold
@@ -193,7 +198,11 @@ def run(
193198
subsetStampMIs.append(statisticsStack(tempStampMIs, stackTypeProperty, statisticsControl))
194199
# TODO: what to do with remaining temp stamps?
195200
tempStampMIs = []
201+
used_stars += self.config.subsetStampNumber
196202

203+
self.metadata["psfStarCount"] = {}
204+
self.metadata["psfStarCount"]["all"] = all_stars
205+
self.metadata["psfStarCount"]["used"] = used_stars
197206
# TODO: which stamp mask plane to use here?
198207
# TODO: Amir: there might be cases where subsetStampMIs is an empty list. What do we want to do then?
199208
# Currently, we get an "IndexError: list index out of range"

python/lsst/pipe/tasks/brightStarSubtraction/brightStarSubtract.py

Whitespace-only changes.

0 commit comments

Comments
 (0)