22 from contextlib
import contextmanager
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.
backgroundbackground.undersampleStyle =
"REDUCE_INTERP_ORDER"
45 self.
backgroundbackground.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 The task implements a pixel-based and an image-based correction estimator.
60 ConfigClass = ScaleVarianceConfig
61 _DefaultName =
"scaleVariance"
64 Task.__init__(self, *args, **kwargs)
65 self.makeSubtask(
"background")
69 """Context manager for subtracting the background
71 We need to subtract the background so that the entire image
72 (apart from objects, which should be clipped) will have the
73 image/sqrt(variance) distributed about zero.
75 This context manager subtracts the background, and ensures it
80 maskedImage : `lsst.afw.image.MaskedImage`
81 Image+mask+variance to have background subtracted and restored.
85 context : context manager
86 Context manager that ensure the background is restored.
88 bg = self.background.fitBackground(maskedImage)
89 bgImage = bg.getImageF(self.background.config.algorithm, self.background.config.undersampleStyle)
90 maskedImage -= bgImage
94 maskedImage += bgImage
96 def run(self, maskedImage):
97 """Rescale the variance in a maskedImage in place.
101 maskedImage : `lsst.afw.image.MaskedImage`
102 Image for which to determine the variance rescaling factor. The image
103 is modified in place.
108 Variance rescaling factor.
113 If the estimated variance rescaling factor by both methods exceed the
118 The task calculates and applies the pixel-based correction unless
119 it is over the ``config.limit`` threshold. In this case, the image-based
123 factor = self.
pixelBasedpixelBased(maskedImage)
124 if factor > self.config.limit:
125 self.log.warning(
"Pixel-based variance rescaling factor (%f) exceeds configured limit (%f); "
126 "trying image-based method", factor, self.config.limit)
127 factor = self.
imageBasedimageBased(maskedImage)
128 if factor > self.config.limit:
129 raise RuntimeError(
"Variance rescaling factor (%f) exceeds configured limit (%f)" %
130 (factor, self.config.limit))
131 self.log.info(
"Renormalizing variance by %f", factor)
132 maskedImage.variance *= factor
136 """Calculate and return both variance scaling factors without modifying the image.
140 maskedImage : `lsst.afw.image.MaskedImage`
141 Image for which to determine the variance rescaling factor.
145 R : `lsst.pipe.base.Struct`
146 - ``pixelFactor`` : `float` The pixel based variance rescaling factor
147 or 1 if all pixels are masked or invalid.
148 - ``imageFactor`` : `float` The image based variance rescaling factor
149 or 1 if all pixels are masked or invalid.
152 pixelFactor = self.
pixelBasedpixelBased(maskedImage)
153 imageFactor = self.
imageBasedimageBased(maskedImage)
154 return Struct(pixelFactor=pixelFactor, imageFactor=imageFactor)
157 """Determine the variance rescaling factor from pixel statistics
159 We calculate SNR = image/sqrt(variance), and the distribution
160 for most of the background-subtracted image should have a standard
161 deviation of unity. We use the interquartile range as a robust estimator
162 of the SNR standard deviation. The variance rescaling factor is the
163 factor that brings that distribution to have unit standard deviation.
165 This may not work well if the image has a lot of structure in it, as
166 the assumptions are violated. In that case, use an alternate
171 maskedImage : `lsst.afw.image.MaskedImage`
172 Image for which to determine the variance rescaling factor.
177 Variance rescaling factor or 1 if all pixels are masked or non-finite.
180 maskVal = maskedImage.mask.getPlaneBitMask(self.config.maskPlanes)
181 isGood = (((maskedImage.mask.array & maskVal) == 0)
182 & np.isfinite(maskedImage.image.array)
183 & np.isfinite(maskedImage.variance.array)
184 & (maskedImage.variance.array > 0))
186 nGood = np.sum(isGood)
187 self.log.debug(
"Number of selected background pixels: %d of %d.", nGood, isGood.size)
193 snr = maskedImage.image.array[isGood]/np.sqrt(maskedImage.variance.array[isGood])
194 q1, q3 = np.percentile(snr, (25, 75))
195 stdev = 0.74*(q3 - q1)
199 """Determine the variance rescaling factor from image statistics
201 We calculate average(SNR) = stdev(image)/median(variance), and
202 the value should be unity. We use the interquartile range as a robust
203 estimator of the stdev. The variance rescaling factor is the
204 factor that brings this value to unity.
206 This may not work well if the pixels from which we measure the
207 standard deviation of the image are not effectively the same pixels
208 from which we measure the median of the variance. In that case, use
213 maskedImage : `lsst.afw.image.MaskedImage`
214 Image for which to determine the variance rescaling factor.
219 Variance rescaling factor or 1 if all pixels are masked or non-finite.
221 maskVal = maskedImage.mask.getPlaneBitMask(self.config.maskPlanes)
222 isGood = (((maskedImage.mask.array & maskVal) == 0)
223 & np.isfinite(maskedImage.image.array)
224 & np.isfinite(maskedImage.variance.array)
225 & (maskedImage.variance.array > 0))
226 nGood = np.sum(isGood)
227 self.log.debug(
"Number of selected background pixels: %d of %d.", nGood, isGood.size)
233 q1, q3 = np.percentile(maskedImage.image.array[isGood], (25, 75))
234 ratio = 0.74*(q3 - q1)/np.sqrt(np.median(maskedImage.variance.array[isGood]))
def computeScaleFactors(self, maskedImage)
def subtractedBackground(self, maskedImage)
def pixelBased(self, maskedImage)
def run(self, maskedImage)
def imageBased(self, maskedImage)
def __init__(self, *args, **kwargs)