22 from contextlib
import contextmanager
25 from lsst.pex.config
import Config, Field, ListField, ConfigurableField
29 __all__ = [
"ScaleVarianceConfig",
"ScaleVarianceTask"]
33 background = ConfigurableField(target=SubtractBackgroundTask, doc=
"Background subtraction")
34 maskPlanes = ListField(
36 default=[
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"SAT",
"NO_DATA",
"INTRP"],
37 doc=
"Mask planes for pixels to ignore when scaling variance",
39 limit = Field(dtype=float, default=10.0, doc=
"Maximum variance scaling value to permit")
44 self.
background.undersampleStyle =
"REDUCE_INTERP_ORDER"
45 self.
background.ignoredPixelMask = [
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"SAT",
"NO_DATA",
"INTRP"]
49 """Scale the variance in a MaskedImage
51 The variance plane in a convolved or warped image (or a coadd derived
52 from warped images) does not accurately reflect the noise properties of
53 the image because variance has been lost to covariance. This Task
54 attempts to correct for this by scaling the variance plane to match
55 the observed variance in the image. This is not perfect (because we're
56 not tracking the covariance) but it's simple and is often good enough.
58 ConfigClass = ScaleVarianceConfig
59 _DefaultName =
"scaleVariance"
62 Task.__init__(self, *args, **kwargs)
67 """Context manager for subtracting the background
69 We need to subtract the background so that the entire image
70 (apart from objects, which should be clipped) will have the
71 image/sqrt(variance) distributed about zero.
73 This context manager subtracts the background, and ensures it
78 maskedImage : `lsst.afw.image.MaskedImage`
79 Image+mask+variance to have background subtracted and restored.
83 context : context manager
84 Context manager that ensure the background is restored.
86 bg = self.background.fitBackground(maskedImage)
87 bgImage = bg.getImageF(self.background.config.algorithm, self.background.config.undersampleStyle)
88 maskedImage -= bgImage
92 maskedImage += bgImage
94 def run(self, maskedImage):
95 """Rescale the variance in a maskedImage
99 maskedImage : `lsst.afw.image.MaskedImage`
100 Image for which to determine the variance rescaling factor.
105 Variance rescaling factor.
110 If the estimated variance rescaling factor exceeds the
115 if np.isnan(factor)
or factor > self.
config.limit:
116 self.
log.
warn(
"Pixel-based variance rescaling factor (%f) exceeds configured limit (%f); "
117 "trying image-based method", factor, self.
config.limit)
119 if np.isnan(factor)
or factor > self.
config.limit:
120 raise RuntimeError(
"Variance rescaling factor (%f) exceeds configured limit (%f)" %
121 (factor, self.
config.limit))
122 self.
log.
info(
"Renormalizing variance by %f" % (factor,))
123 maskedImage.variance *= factor
127 """Determine the variance rescaling factor from pixel statistics
129 We calculate SNR = image/sqrt(variance), and the distribution
130 for most of the background-subtracted image should have a standard
131 deviation of unity. The variance rescaling factor is the factor that
132 brings that distribution to have unit standard deviation.
134 This may not work well if the image has a lot of structure in it, as
135 the assumptions are violated. In that case, use an alternate
140 maskedImage : `lsst.afw.image.MaskedImage`
141 Image for which to determine the variance rescaling factor.
146 Variance rescaling factor.
148 variance = maskedImage.variance
149 snr = maskedImage.image.array/np.sqrt(variance.array)
150 maskVal = maskedImage.mask.getPlaneBitMask(self.
config.maskPlanes)
151 isGood = ((maskedImage.mask.array & maskVal) == 0) & (maskedImage.variance.array > 0)
154 q1, q3 = np.percentile(snr[isGood], (25, 75))
161 stdev = 0.74*(q3 - q1)
165 """Determine the variance rescaling factor from image statistics
167 We calculate average(SNR) = stdev(image)/median(variance), and
168 the value should be unity. The variance rescaling factor is the
169 factor that brings this value to unity.
171 This may not work well if the pixels from which we measure the
172 standard deviation of the image are not effectively the same pixels
173 from which we measure the median of the variance. In that case, use
178 maskedImage : `lsst.afw.image.MaskedImage`
179 Image for which to determine the variance rescaling factor.
184 Variance rescaling factor.
186 maskVal = maskedImage.mask.getPlaneBitMask(self.
config.maskPlanes)
187 isGood = ((maskedImage.mask.array & maskVal) == 0) & (maskedImage.variance.array > 0)
189 q1, q3 = np.percentile(maskedImage.image.array[isGood], (25, 75))
190 ratio = 0.74*(q3 - q1)/np.sqrt(np.median(maskedImage.variance.array[isGood]))