120 templateMatched=
True, preConvMode=
False, **kwargs):
121 """Perform decorrelation of an image difference or of a score difference exposure.
123 Corrects the difference or score image due to the convolution of the
124 templateExposure with the A&L PSF matching kernel.
125 See [DMTN-021, Equation 1](http://dmtn-021.lsst.io/#equation-1) and
126 [DMTN-179](http://dmtn-179.lsst.io/) for details.
130 scienceExposure : `lsst.afw.image.Exposure`
131 The original science exposure (before pre-convolution, if ``preConvMode==True``).
132 templateExposure : `lsst.afw.image.Exposure`
133 The original template exposure warped into the science exposure dimensions.
134 subtractedExposure : `lsst.afw.image.Exposure`
135 the subtracted exposure produced by
136 `ip_diffim.ImagePsfMatchTask.subtractExposures()`. The `subtractedExposure` must
137 inherit its PSF from `exposure`, see notes below.
138 psfMatchingKernel : `lsst.afw.detection.Psf`
139 An (optionally spatially-varying) PSF matching kernel produced
140 by `ip_diffim.ImagePsfMatchTask.subtractExposures()`.
141 preConvKernel : `lsst.afw.math.Kernel`, optional
142 If not `None`, then the `scienceExposure` was pre-convolved with (the reflection of)
143 this kernel. Must be normalized to sum to 1.
144 Allowed only if ``templateMatched==True`` and ``preConvMode==True``.
145 Defaults to the PSF of the science exposure at the image center.
146 xcen : `float`, optional
147 X-pixel coordinate to use for computing constant matching kernel to use
148 If `None` (default), then use the center of the image.
149 ycen : `float`, optional
150 Y-pixel coordinate to use for computing constant matching kernel to use
151 If `None` (default), then use the center of the image.
152 svar : `float`, optional
153 Image variance for science image
154 If `None` (default) then compute the variance over the entire input science image.
155 tvar : `float`, optional
156 Image variance for template image
157 If `None` (default) then compute the variance over the entire input template image.
158 templateMatched : `bool`, optional
159 If True, the template exposure was matched (convolved) to the science exposure.
160 See also notes below.
161 preConvMode : `bool`, optional
162 If True, ``subtractedExposure`` is assumed to be a likelihood difference image
163 and will be noise corrected as a likelihood image.
165 Additional keyword arguments propagated from DecorrelateALKernelSpatialTask.
169 result : `lsst.pipe.base.Struct`
170 - ``correctedExposure`` : the decorrelated diffim
174 If ``preConvMode==True``, ``subtractedExposure`` is assumed to be a
175 score image and the noise correction for likelihood images
176 is applied. The resulting image is an optimal detection likelihood image
177 when the templateExposure has noise. (See DMTN-179) If ``preConvKernel`` is
178 not specified, the PSF of ``scienceExposure`` is assumed as pre-convolution kernel.
180 The ``subtractedExposure`` is NOT updated. The returned ``correctedExposure``
181 has an updated but spatially fixed PSF. It is calculated as the center of
182 image PSF corrected by the center of image matching kernel.
184 If ``templateMatched==True``, the templateExposure was matched (convolved)
185 to the ``scienceExposure`` by ``psfMatchingKernel``. Otherwise the ``scienceExposure``
186 was matched (convolved) by ``psfMatchingKernel``.
188 This task discards the variance plane of ``subtractedExposure`` and re-computes
189 it from the variance planes of ``scienceExposure`` and ``templateExposure``.
190 The image plane of ``subtractedExposure`` must be at the photometric level
191 set by the AL PSF matching in `ImagePsfMatchTask.subtractExposures`.
192 The assumptions about the photometric level are controlled by the
193 `templateMatched` option in this task.
195 Here we currently convert a spatially-varying matching kernel into a constant kernel,
196 just by computing it at the center of the image (tickets DM-6243, DM-6244).
198 We are also using a constant accross-the-image measure of sigma (sqrt(variance)) to compute
199 the decorrelation kernel.
201 TODO DM-23857 As part of the spatially varying correction implementation
202 consider whether returning a Struct is still necessary.
204 if preConvKernel
is not None and not (templateMatched
and preConvMode):
205 raise ValueError(
"Pre-convolution kernel is allowed only if "
206 "preConvMode==True and templateMatched==True.")
208 spatialKernel = psfMatchingKernel
209 kimg = afwImage.ImageD(spatialKernel.getDimensions())
210 bbox = subtractedExposure.getBBox()
212 xcen = (bbox.getBeginX() + bbox.getEndX()) / 2.
214 ycen = (bbox.getBeginY() + bbox.getEndY()) / 2.
215 self.log.
info(
"Using matching kernel computed at (%d, %d)", xcen, ycen)
216 spatialKernel.computeImage(kimg,
False, xcen, ycen)
220 if preConvKernel
is None:
221 preConvKernel = scienceExposure.getPsf().getLocalKernel()
222 preConvImg = afwImage.ImageD(preConvKernel.getDimensions())
223 preConvKernel.computeImage(preConvImg,
True)
226 svar = self.computeVarianceMean(scienceExposure)
228 tvar = self.computeVarianceMean(templateExposure)
229 self.log.
info(
"Original variance plane means. Science:%.5e, warped template:%.5e)",
234 self.log.
info(
"Decorrelation after template image convolution")
237 exposure = scienceExposure
238 matchedExposure = templateExposure
241 self.log.
info(
"Decorrelation after science image convolution")
244 exposure = templateExposure
245 matchedExposure = scienceExposure
250 if np.isnan(expVar)
or np.isnan(matchedVar):
252 if (np.all(np.isnan(exposure.image.array))
253 or np.all(np.isnan(matchedExposure.image.array))):
254 self.log.
warning(
'Template or science image is entirely NaNs: skipping decorrelation.')
255 outExposure = subtractedExposure.clone()
256 return pipeBase.Struct(correctedExposure=outExposure, )
260 mOverExpVar = matchedVar/expVar
261 if mOverExpVar > 1e8:
262 self.log.
warning(
"Diverging correction: matched image variance is "
263 " much larger than the unconvolved one's"
264 ", matchedVar/expVar:%.2e", mOverExpVar)
266 oldVarMean = self.computeVarianceMean(subtractedExposure)
267 self.log.
info(
"Variance plane mean of uncorrected diffim: %f", oldVarMean)
270 diffExpArr = subtractedExposure.image.array
271 psfImg = subtractedExposure.getPsf().computeKernelImage(
geom.Point2D(xcen, ycen))
272 psfDim = psfImg.getDimensions()
273 psfArr = psfImg.array
278 self.log.
debug(
"Matching kernel sum: %.3e", kSum)
281 self.log.
info(
"Decorrelation of likelihood image")
282 self.computeCommonShape(preConvImg.array.shape, kArr.shape,
283 psfArr.shape, diffExpArr.shape)
284 corr = self.computeScoreCorrection(kArr, expVar, matchedVar, preConvImg.array)
286 self.log.
info(
"Decorrelation of difference image")
287 self.computeCommonShape(kArr.shape, psfArr.shape, diffExpArr.shape)
288 corr = self.computeDiffimCorrection(kArr, expVar, matchedVar)
290 diffExpArr = self.computeCorrectedImage(corr.corrft, diffExpArr)
292 corrPsfArr = self.computeCorrectedDiffimPsf(corr.corrft, psfArr)
293 psfcI = afwImage.ImageD(psfDim)
294 psfcI.array = corrPsfArr
296 psfNew = measAlg.KernelPsf(psfcK)
298 correctedExposure = subtractedExposure.clone()
299 correctedExposure.image.array[...] = diffExpArr
303 if self.config.completeVarPlanePropagation:
304 self.log.
debug(
"Using full variance plane calculation in decorrelation")
305 newVarArr = self.calculateVariancePlane(
306 exposure.variance.array, matchedExposure.variance.array,
307 expVar, matchedVar, corr.cnft, corr.crft)
309 self.log.
debug(
"Using estimated variance plane calculation in decorrelation")
310 newVarArr = self.estimateVariancePlane(
311 exposure.variance.array, matchedExposure.variance.array,
312 corr.cnft, corr.crft)
314 corrExpVarArr = correctedExposure.variance.array
315 corrExpVarArr[...] = newVarArr
317 if not templateMatched:
320 corrExpVarArr /= kSumSq
321 correctedExposure.setPsf(psfNew)
323 newVarMean = self.computeVarianceMean(correctedExposure)
324 self.log.
info(
"Variance plane mean of corrected diffim: %.5e", newVarMean)
328 return pipeBase.Struct(correctedExposure=correctedExposure, )
A kernel created from an Image.