22from contextlib
import contextmanager
27from .
import SubtractBackgroundTask
29__all__ = [
"ScaleVarianceConfig",
"ScaleVarianceTask"]
33 background = ConfigurableField(target=SubtractBackgroundTask, doc=
"Background subtraction")
34 maskPlanes = ListField[str](
35 default=[
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"SAT",
"NO_DATA",
"INTRP"],
36 doc=
"Mask planes for pixels to ignore when scaling variance",
38 limit = Field[float](default=10.0, doc=
"Maximum variance scaling value to permit")
43 self.
background.undersampleStyle =
"REDUCE_INTERP_ORDER"
44 self.
background.ignoredPixelMask = [
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"SAT",
"NO_DATA",
"INTRP"]
48 """Scale the variance in a MaskedImage
50 The variance plane in a convolved
or warped image (
or a coadd derived
51 from warped images) does
not accurately reflect the noise properties of
52 the image because variance has been lost to covariance. This Task
53 attempts to correct
for this by scaling the variance plane to match
54 the observed variance
in the image. This
is not perfect (because we
're
55 not tracking the covariance) but it
's simple and is often good enough.
57 The task implements a pixel-based and an image-based correction estimator.
59 ConfigClass = ScaleVarianceConfig
60 _DefaultName = "scaleVariance"
63 Task.__init__(self, *args, **kwargs)
64 self.makeSubtask(
"background")
68 """Context manager for subtracting the background
70 We need to subtract the background so that the entire image
71 (apart from objects, which should be clipped) will have the
72 image/sqrt(variance) distributed about zero.
74 This context manager subtracts the background,
and ensures it
80 Image+mask+variance to have background subtracted
and restored.
84 context : context manager
85 Context manager that ensure the background
is restored.
87 bg = self.background.fitBackground(maskedImage)
88 bgImage = bg.getImageF(self.background.config.algorithm, self.background.config.undersampleStyle)
89 maskedImage -= bgImage
93 maskedImage += bgImage
95 def run(self, maskedImage):
96 """Rescale the variance in a maskedImage in place.
101 Image for which to determine the variance rescaling factor. The image
102 is modified
in place.
107 Variance rescaling factor.
112 If the estimated variance rescaling factor by both methods exceed the
117 The task calculates
and applies the pixel-based correction unless
118 it
is over the ``config.limit`` threshold. In this case, the image-based
123 if factor > self.config.limit:
124 self.log.warning(
"Pixel-based variance rescaling factor (%f) exceeds configured limit (%f); "
125 "trying image-based method", factor, self.config.limit)
127 if factor > self.config.limit:
128 raise RuntimeError(
"Variance rescaling factor (%f) exceeds configured limit (%f)" %
129 (factor, self.config.limit))
130 self.log.info(
"Renormalizing variance by %f", factor)
131 maskedImage.variance *= factor
135 """Calculate and return both variance scaling factors without modifying the image.
140 Image for which to determine the variance rescaling factor.
144 R : `lsst.pipe.base.Struct`
145 - ``pixelFactor`` : `float` The pixel based variance rescaling factor
146 or 1
if all pixels are masked
or invalid.
147 - ``imageFactor`` : `float` The image based variance rescaling factor
148 or 1
if all pixels are masked
or invalid.
153 return Struct(pixelFactor=pixelFactor, imageFactor=imageFactor)
156 """Determine the variance rescaling factor from pixel statistics
158 We calculate SNR = image/sqrt(variance), and the distribution
159 for most of the background-subtracted image should have a standard
160 deviation of unity. We use the interquartile range
as a robust estimator
161 of the SNR standard deviation. The variance rescaling factor
is the
162 factor that brings that distribution to have unit standard deviation.
164 This may
not work well
if the image has a lot of structure
in it,
as
165 the assumptions are violated. In that case, use an alternate
171 Image
for which to determine the variance rescaling factor.
176 Variance rescaling factor
or 1
if all pixels are masked
or non-finite.
179 maskVal = maskedImage.mask.getPlaneBitMask(self.config.maskPlanes)
180 isGood = (((maskedImage.mask.array & maskVal) == 0)
181 & np.isfinite(maskedImage.image.array)
182 & np.isfinite(maskedImage.variance.array)
183 & (maskedImage.variance.array > 0))
185 nGood = np.sum(isGood)
186 self.log.debug("Number of selected background pixels: %d of %d.", nGood, isGood.size)
192 snr = maskedImage.image.array[isGood]/np.sqrt(maskedImage.variance.array[isGood])
193 q1, q3 = np.percentile(snr, (25, 75))
194 stdev = 0.74*(q3 - q1)
198 """Determine the variance rescaling factor from image statistics
200 We calculate average(SNR) = stdev(image)/median(variance), and
201 the value should be unity. We use the interquartile range
as a robust
202 estimator of the stdev. The variance rescaling factor
is the
203 factor that brings this value to unity.
205 This may
not work well
if the pixels
from which we measure the
206 standard deviation of the image are
not effectively the same pixels
207 from which we measure the median of the variance. In that case, use
213 Image
for which to determine the variance rescaling factor.
218 Variance rescaling factor
or 1
if all pixels are masked
or non-finite.
220 maskVal = maskedImage.mask.getPlaneBitMask(self.config.maskPlanes)
221 isGood = (((maskedImage.mask.array & maskVal) == 0)
222 & np.isfinite(maskedImage.image.array)
223 & np.isfinite(maskedImage.variance.array)
224 & (maskedImage.variance.array > 0))
225 nGood = np.sum(isGood)
226 self.log.debug("Number of selected background pixels: %d of %d.", nGood, isGood.size)
232 q1, q3 = np.percentile(maskedImage.image.array[isGood], (25, 75))
233 ratio = 0.74*(q3 - q1)/np.sqrt(np.median(maskedImage.variance.array[isGood]))
imageBased(self, maskedImage)
computeScaleFactors(self, maskedImage)
subtractedBackground(self, maskedImage)
__init__(self, *args, **kwargs)
pixelBased(self, maskedImage)