24from contextlib
import contextmanager
25from dataclasses
import dataclass
34 "ComputeNoiseCorrelationConfig",
35 "ComputeNoiseCorrelationTask",
40@dataclass(frozen=True)
42 """A class holding correlation coefficients for a set of background pixels.
44 CorrelationMatrix is a dataclass that
is initialized
with a numpy ndarray
45 and provides some convenience methods
for accessing the matrix elements.
46 A CorrelationMatrix instance
is callable wth two integer values x
and y,
47 which returns the <I(m,n) I(m+x, n+y) / sqrt( V(m,n) V(m+x,n+y) )>, where
48 I
is the image, V
is the variance plane
and < > denotes the expectation
53 array : `numpy.ndarray`
54 The matrix of correlation coefficients.
60 def shape(self) -> tuple[int, int]:
61 """The shape of the correlation matrix."""
62 return self.array.shape
65 return self.array[x, y]
70 target=SubtractBackgroundTask, doc=
"Background subtraction"
72 maskPlanes = ListField[str](
73 default=[
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"SAT",
"NO_DATA",
"INTRP"],
74 doc=
"Mask planes for pixels to ignore when calculating correlations",
76 size = Field[int](default=5, doc=
"Size of the correlation matrix to produce")
77 scaleEmpiricalVariance = Field[bool](
80 "Scale down the correlation coefficients x by the empirical variance of the background "
81 "in addition to the variance plane?"
84 subtractEmpiricalMean = Field[bool](
85 default=
False, doc=
"Subtract the empirical mean in addition to the background?"
91 self.
background.undersampleStyle =
"REDUCE_INTERP_ORDER"
103 """Compute the noise correlation coefficients in a MaskedImage
105 The variance plane in a convolved
or warped image (
or a coadd derived
106 from warped images) does
not accurately reflect the noise properties of
107 the image because variance has been lost to covariance. This Task computes
108 a matrix of correlation coefficients of a desired size. It assumes that the
109 noise
is (at least the correlation coefficients are) stationary
and uses
110 spatial averaging to compute the correlation coefficients.
113 ConfigClass = ComputeNoiseCorrelationConfig
114 _DefaultName = "computeNoiseCorrelation"
118 self.makeSubtask(
"background")
119 self.background: SubtractBackgroundTask
123 """Context manager for subtracting the background
125 We need to subtract the background so that the entire image
126 (apart from objects, which should be clipped) will have the
127 image/sqrt(variance) distributed about zero
with unit variance.
128 This context manager subtracts the background,
and ensures it
134 Image+mask+variance to have background subtracted
and restored.
138 context : context manager
139 Context manager that ensure the background
is restored.
141 bg = self.background.fitBackground(maskedImage)
142 bgImage = bg.getImageF(
143 self.background.config.algorithm, self.background.config.undersampleStyle
145 maskedImage -= bgImage
149 maskedImage += bgImage
155 ) -> CorrelationMatrix:
156 """Compute the correlation matrix from a maskedImage.
161 Image for which to determine the correlation matrix.
163 Image
from which to determine which pixels to mask.
164 If
None, it defaults to ``maskedImage``.
168 corr_matrix : `CorrelationMatrix`
169 Correlation matrix of the maskedImage.
174 Raised
if ``refMaskedImage``
is provided
and does
not have the same
175 dimensions
as ``maskedImage``.
178 if refMaskedImage
is None:
179 refMaskedImage = maskedImage
180 elif refMaskedImage.getDimensions() != maskedImage.getDimensions():
182 "Reference image has different dimensions than input image"
185 corr_matrix = self.
_pixelBased(maskedImage, refMaskedImage)
193 ) -> CorrelationMatrix:
194 """Determine correlation coefficients between pixels
196 This is the concrete routine that does the computation.
201 Image
for which to determine the variance rescaling factor.
203 Image
from which to determine which pixels to mask.
207 corr_matrix : `CorrelationMatrix`
208 Correlation matrix of the maskedImage.
210 maskVal = refMaskedImage.mask.getPlaneBitMask(self.config.maskPlanes)
212 ((refMaskedImage.mask.array & maskVal) == 0)
213 & np.isfinite(refMaskedImage.image.array)
214 & np.isfinite(refMaskedImage.variance.array)
215 & (refMaskedImage.variance.array > 0)
218 nGood = np.sum(isGood)
220 "Number of selected background pixels: %d of %d.", nGood, isGood.size
225 with warnings.catch_warnings():
226 warnings.simplefilter(
"ignore", category=RuntimeWarning)
227 normalized_arr = maskedImage.image.array / np.sqrt(
228 maskedImage.variance.array
230 normalized_arr[~isGood] = np.nan
232 corr_matrix = np.empty(
233 (self.config.size + 1, self.config.size + 1), dtype=np.float32
236 for dx, dy
in itertools.product(
237 range(self.config.size + 1), range(self.config.size + 1)
239 sliceX = slice(
None, -dx)
if dx != 0
else slice(
None,
None)
240 sliceY = slice(
None, -dy)
if dy != 0
else slice(
None,
None)
241 arr1 = normalized_arr[sliceX, sliceY]
243 sliceX = slice(dx,
None)
if dx != 0
else slice(
None,
None)
244 sliceY = slice(dy,
None)
if dy != 0
else slice(
None,
None)
245 arr2 = normalized_arr[sliceX, sliceY]
247 if self.config.subtractEmpiricalMean:
248 arr1 -= np.nanmean(arr1)
249 arr2 -= np.nanmean(arr2)
250 if self.config.scaleEmpiricalVariance:
253 arr1 /= np.nanmean(arr1**2) ** 0.5
254 arr2 /= np.nanmean(arr2**2) ** 0.5
256 cov = np.nanmean(arr1 * arr2)
260 cov *= 1.0 + 0.5 * (1 - cov**2) / (~np.isnan(arr1 * arr2)).sum()
262 corr_matrix[dx, dy] = cov
A class to manipulate images, masks, and variance as a single object.
def __init__(self, *args, **kwargs)
CorrelationMatrix _pixelBased(self, lsst.afw.image.MaskedImage maskedImage, lsst.afw.image.MaskedImage refMaskedImage)
def subtractedBackground(self, lsst.afw.image.MaskedImage maskedImage)
tuple[int, int] shape(self)
float __call__(self, int x, int y)