lsst.ip.diffim  13.0-22-g3839dbb
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
imageDecorrelation.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 from future import standard_library
3 standard_library.install_aliases()
4 #
5 # LSST Data Management System
6 # Copyright 2016 AURA/LSST.
7 #
8 # This product includes software developed by the
9 # LSST Project (http://www.lsst.org/).
10 #
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the LSST License Statement and
22 # the GNU General Public License along with this program. If not,
23 # see <https://www.lsstcorp.org/LegalNotices/>.
24 #
25 
26 import numpy as np
27 import scipy.fftpack
28 
29 import lsst.afw.image as afwImage
30 import lsst.afw.geom as afwGeom
31 import lsst.meas.algorithms as measAlg
32 import lsst.afw.math as afwMath
33 import lsst.pex.config as pexConfig
34 import lsst.pipe.base as pipeBase
35 import lsst.log
36 
37 from .imageMapReduce import (ImageMapReduceConfig, ImageMapReduceTask,
38  ImageMapperSubtask)
39 
40 __all__ = ("DecorrelateALKernelTask", "DecorrelateALKernelConfig",
41  "DecorrelateALKernelMapperSubtask", "DecorrelateALKernelMapReduceConfig",
42  "DecorrelateALKernelSpatialConfig", "DecorrelateALKernelSpatialTask")
43 
44 
45 class DecorrelateALKernelConfig(pexConfig.Config):
46  """!
47  \anchor DecorrelateALKernelConfig_
48 
49  \brief Configuration parameters for the DecorrelateALKernelTask
50  """
51 
52  ignoreMaskPlanes = pexConfig.ListField(
53  dtype=str,
54  doc="""Mask planes to ignore for sigma-clipped statistics""",
55  default=("INTRP", "EDGE", "DETECTED", "SAT", "CR", "BAD", "NO_DATA", "DETECTED_NEGATIVE")
56  )
57 
58 ## \addtogroup LSST_task_documentation
59 ## \{
60 ## \page DecorrelateALKernelTask
61 ## \ref DecorrelateALKernelTask_ "DecorrelateALKernelTask"
62 ## Decorrelate the effect of convolution by Alard-Lupton matching kernel in image difference
63 ## \}
64 
65 
66 class DecorrelateALKernelTask(pipeBase.Task):
67  """!
68  \anchor DecorrelateALKernelTask_
69 
70  \brief Decorrelate the effect of convolution by Alard-Lupton matching kernel in image difference
71 
72  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Contents Contents
73 
74  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Purpose
75  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Config
76  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Run
77  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Debug
78  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Example
79 
80  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Purpose Description
81 
82  Pipe-task that removes the neighboring-pixel covariance in an
83  image difference that are added when the template image is
84  convolved with the Alard-Lupton PSF matching kernel.
85 
86  The image differencing pipeline task \link
87  ip.diffim.psfMatch.PsfMatchTask PSFMatchTask\endlink and \link
88  ip.diffim.psfMatch.PsfMatchConfigAL PSFMatchConfigAL\endlink uses
89  the Alard and Lupton (1998) method for matching the PSFs of the
90  template and science exposures prior to subtraction. The
91  Alard-Lupton method identifies a matching kernel, which is then
92  (typically) convolved with the template image to perform PSF
93  matching. This convolution has the effect of adding covariance
94  between neighboring pixels in the template image, which is then
95  added to the image difference by subtraction.
96 
97  The pixel covariance may be corrected by whitening the noise of
98  the image difference. This task performs such a decorrelation by
99  computing a decorrelation kernel (based upon the A&L matching
100  kernel and variances in the template and science images) and
101  convolving the image difference with it. This process is described
102  in detail in [DMTN-021](http://dmtn-021.lsst.io).
103 
104  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Initialize Task initialization
105 
106  \copydoc \_\_init\_\_
107 
108  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Run Invoking the Task
109 
110  \copydoc run
111 
112  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Config Configuration parameters
113 
114  See \ref DecorrelateALKernelConfig
115 
116  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Debug Debug variables
117 
118  This task has no debug variables
119 
120  \section ip_diffim_imageDecorrelation_DecorrelateALKernelTask_Example Example of using DecorrelateALKernelTask
121 
122  This task has no standalone example, however it is applied as a
123  subtask of pipe.tasks.imageDifference.ImageDifferenceTask.
124  """
125  ConfigClass = DecorrelateALKernelConfig
126  _DefaultName = "ip_diffim_decorrelateALKernel"
127 
128  def __init__(self, *args, **kwargs):
129  """! Create the image decorrelation Task
130  @param *args arguments to be passed to lsst.pipe.base.task.Task.__init__
131  @param **kwargs keyword arguments to be passed to lsst.pipe.base.task.Task.__init__
132  """
133  pipeBase.Task.__init__(self, *args, **kwargs)
134 
135  self.statsControl = afwMath.StatisticsControl()
136  self.statsControl.setNumSigmaClip(3.)
137  self.statsControl.setNumIter(3)
138  self.statsControl.setAndMask(afwImage.Mask\
139  .getPlaneBitMask(self.config.ignoreMaskPlanes))
140 
141  def computeVarianceMean(self, exposure):
142  statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(),
143  exposure.getMaskedImage().getMask(),
144  afwMath.MEANCLIP, self.statsControl)
145  var = statObj.getValue(afwMath.MEANCLIP)
146  return var
147 
148  @pipeBase.timeMethod
149  def run(self, exposure, templateExposure, subtractedExposure, psfMatchingKernel,
150  xcen=None, ycen=None, svar=None, tvar=None):
151  """! Perform decorrelation of an image difference exposure.
152 
153  Decorrelates the diffim due to the convolution of the templateExposure with the
154  A&L PSF matching kernel. Currently can accept a spatially varying matching kernel but in
155  this case it simply uses a static kernel from the center of the exposure. The decorrelation
156  is described in [DMTN-021, Equation 1](http://dmtn-021.lsst.io/#equation-1), where
157  `exposure` is I_1; templateExposure is I_2; `subtractedExposure` is D(k);
158  `psfMatchingKernel` is kappa; and svar and tvar are their respective
159  variances (see below).
160 
161  @param[in] exposure the science afwImage.Exposure used for PSF matching
162  @param[in] templateExposure the template afwImage.Exposure used for PSF matching
163  @param[in] subtractedExposure the subtracted exposure produced by
164  `ip_diffim.ImagePsfMatchTask.subtractExposures()`
165  @param[in] psfMatchingKernel an (optionally spatially-varying) PSF matching kernel produced
166  by `ip_diffim.ImagePsfMatchTask.subtractExposures()`
167  @param[in] xcen X-pixel coordinate to use for computing constant matching kernel to use
168  If `None` (default), then use the center of the image.
169  @param[in] ycen Y-pixel coordinate to use for computing constant matching kernel to use
170  If `None` (default), then use the center of the image.
171  @param[in] svar image variance for science image
172  If `None` (default) then compute the variance over the entire input science image.
173  @param[in] tvar image variance for template image
174  If `None` (default) then compute the variance over the entire input template image.
175 
176  @return a `pipeBase.Struct` containing:
177  * `correctedExposure`: the decorrelated diffim
178  * `correctionKernel`: the decorrelation correction kernel (which may be ignored)
179 
180  @note The `subtractedExposure` is NOT updated
181  @note The returned `correctedExposure` has an updated PSF as well.
182  @note Here we currently convert a spatially-varying matching kernel into a constant kernel,
183  just by computing it at the center of the image (tickets DM-6243, DM-6244).
184  @note We are also using a constant accross-the-image measure of sigma (sqrt(variance)) to compute
185  the decorrelation kernel.
186  @note Still TBD (ticket DM-6580): understand whether the convolution is correctly modifying
187  the variance plane of the new subtractedExposure.
188  """
189  spatialKernel = psfMatchingKernel
190  kimg = afwImage.ImageD(spatialKernel.getDimensions())
191  bbox = subtractedExposure.getBBox()
192  if xcen is None:
193  xcen = (bbox.getBeginX() + bbox.getEndX()) / 2.
194  if ycen is None:
195  ycen = (bbox.getBeginY() + bbox.getEndY()) / 2.
196  self.log.info("Using matching kernel computed at (%d, %d)", xcen, ycen)
197  spatialKernel.computeImage(kimg, True, xcen, ycen)
198 
199  if svar is None:
200  svar = self.computeVarianceMean(exposure)
201  if tvar is None:
202  tvar = self.computeVarianceMean(templateExposure)
203  self.log.info("Variance (science, template): (%f, %f)", svar, tvar)
204 
205  var = self.computeVarianceMean(subtractedExposure)
206  self.log.info("Variance (uncorrected diffim): %f", var)
207 
208  corrKernel = DecorrelateALKernelTask._computeDecorrelationKernel(kimg.getArray(), svar, tvar)
209  correctedExposure, corrKern = DecorrelateALKernelTask._doConvolve(subtractedExposure, corrKernel)
210 
211  # Compute the subtracted exposure's updated psf
212  psf = subtractedExposure.getPsf().computeKernelImage(afwGeom.Point2D(xcen, ycen)).getArray()
213  psfc = DecorrelateALKernelTask.computeCorrectedDiffimPsf(corrKernel, psf, svar=svar, tvar=tvar)
214  psfcI = afwImage.ImageD(psfc.shape[0], psfc.shape[1])
215  psfcI.getArray()[:, :] = psfc
216  psfcK = afwMath.FixedKernel(psfcI)
217  psfNew = measAlg.KernelPsf(psfcK)
218  correctedExposure.setPsf(psfNew)
219 
220  var = self.computeVarianceMean(correctedExposure)
221  self.log.info("Variance (corrected diffim): %f", var)
222 
223  return pipeBase.Struct(correctedExposure=correctedExposure, correctionKernel=corrKern)
224 
225  @staticmethod
226  def _computeDecorrelationKernel(kappa, svar=0.04, tvar=0.04):
227  """! Compute the Lupton/ZOGY post-conv. kernel for decorrelating an
228  image difference, based on the PSF-matching kernel.
229  @param kappa A matching kernel 2-d numpy.array derived from Alard & Lupton PSF matching
230  @param svar Average variance of science image used for PSF matching
231  @param tvar Average variance of template image used for PSF matching
232  @return a 2-d numpy.array containing the correction kernel
233 
234  @note As currently implemented, kappa is a static (single, non-spatially-varying) kernel.
235  """
236  kappa = DecorrelateALKernelTask._fixOddKernel(kappa)
237  kft = scipy.fftpack.fft2(kappa)
238  kft = np.sqrt((svar + tvar) / (svar + tvar * kft**2))
239  pck = scipy.fftpack.ifft2(kft)
240  pck = scipy.fftpack.ifftshift(pck.real)
241  fkernel = DecorrelateALKernelTask._fixEvenKernel(pck)
242 
243  # I think we may need to "reverse" the PSF, as in the ZOGY (and Kaiser) papers...
244  # This is the same as taking the complex conjugate in Fourier space before FFT-ing back to real space.
245  if False: # TBD: figure this out. For now, we are turning it off.
246  fkernel = fkernel[::-1, :]
247 
248  return fkernel
249 
250  @staticmethod
251  def computeCorrectedDiffimPsf(kappa, psf, svar=0.04, tvar=0.04):
252  """! Compute the (decorrelated) difference image's new PSF.
253  new_psf = psf(k) * sqrt((svar + tvar) / (svar + tvar * kappa_ft(k)**2))
254 
255  @param kappa A matching kernel array derived from Alard & Lupton PSF matching
256  @param psf The uncorrected psf array of the science image (and also of the diffim)
257  @param svar Average variance of science image used for PSF matching
258  @param tvar Average variance of template image used for PSF matching
259  @return a 2-d numpy.array containing the new PSF
260  """
261  def post_conv_psf_ft2(psf, kernel, svar, tvar):
262  # Pad psf or kernel symmetrically to make them the same size!
263  # Note this assumes they are both square (width == height)
264  if psf.shape[0] < kernel.shape[0]:
265  diff = (kernel.shape[0] - psf.shape[0]) // 2
266  psf = np.pad(psf, (diff, diff), mode='constant')
267  elif psf.shape[0] > kernel.shape[0]:
268  diff = (psf.shape[0] - kernel.shape[0]) // 2
269  kernel = np.pad(kernel, (diff, diff), mode='constant')
270  psf_ft = scipy.fftpack.fft2(psf)
271  kft = scipy.fftpack.fft2(kernel)
272  out = psf_ft * np.sqrt((svar + tvar) / (svar + tvar * kft**2))
273  return out
274 
275  def post_conv_psf(psf, kernel, svar, tvar):
276  kft = post_conv_psf_ft2(psf, kernel, svar, tvar)
277  out = scipy.fftpack.ifft2(kft)
278  return out
279 
280  pcf = post_conv_psf(psf=psf, kernel=kappa, svar=svar, tvar=tvar)
281  pcf = pcf.real / pcf.real.sum()
282  return pcf
283 
284  @staticmethod
285  def _fixOddKernel(kernel):
286  """! Take a kernel with odd dimensions and make them even for FFT
287 
288  @param kernel a numpy.array
289  @return a fixed kernel numpy.array. Returns a copy if the dimensions needed to change;
290  otherwise just return the input kernel.
291  """
292  # Note this works best for the FFT if we left-pad
293  out = kernel
294  changed = False
295  if (out.shape[0] % 2) == 1:
296  out = np.pad(out, ((1, 0), (0, 0)), mode='constant')
297  changed = True
298  if (out.shape[1] % 2) == 1:
299  out = np.pad(out, ((0, 0), (1, 0)), mode='constant')
300  changed = True
301  if changed:
302  out *= (np.mean(kernel) / np.mean(out)) # need to re-scale to same mean for FFT
303  return out
304 
305  @staticmethod
306  def _fixEvenKernel(kernel):
307  """! Take a kernel with even dimensions and make them odd, centered correctly.
308  @param kernel a numpy.array
309  @return a fixed kernel numpy.array
310  """
311  # Make sure the peak (close to a delta-function) is in the center!
312  maxloc = np.unravel_index(np.argmax(kernel), kernel.shape)
313  out = np.roll(kernel, kernel.shape[0]//2 - maxloc[0], axis=0)
314  out = np.roll(out, out.shape[1]//2 - maxloc[1], axis=1)
315  # Make sure it is odd-dimensioned by trimming it.
316  if (out.shape[0] % 2) == 0:
317  maxloc = np.unravel_index(np.argmax(out), out.shape)
318  if out.shape[0] - maxloc[0] > maxloc[0]:
319  out = out[:-1, :]
320  else:
321  out = out[1:, :]
322  if out.shape[1] - maxloc[1] > maxloc[1]:
323  out = out[:, :-1]
324  else:
325  out = out[:, 1:]
326  return out
327 
328  @staticmethod
329  def _doConvolve(exposure, kernel):
330  """! Convolve an Exposure with a decorrelation convolution kernel.
331  @param exposure Input afw.image.Exposure to be convolved.
332  @param kernel Input 2-d numpy.array to convolve the image with
333  @return a new Exposure with the convolved pixels and the (possibly
334  re-centered) kernel.
335 
336  @note We use afwMath.convolve() but keep scipy.convolve for debugging.
337  @note We re-center the kernel if necessary and return the possibly re-centered kernel
338  """
339  kernelImg = afwImage.ImageD(kernel.shape[0], kernel.shape[1])
340  kernelImg.getArray()[:, :] = kernel
341  kern = afwMath.FixedKernel(kernelImg)
342  maxloc = np.unravel_index(np.argmax(kernel), kernel.shape)
343  kern.setCtrX(maxloc[0])
344  kern.setCtrY(maxloc[1])
345  outExp = exposure.clone() # Do this to keep WCS, PSF, masks, etc.
346  convCntrl = afwMath.ConvolutionControl(False, True, 0)
347  afwMath.convolve(outExp.getMaskedImage(), exposure.getMaskedImage(), kern, convCntrl)
348 
349  return outExp, kern
350 
351 
353  """Task to be used as an ImageMapperSubtask for performing
354  A&L decorrelation on subimages on a grid across a A&L difference image.
355 
356  This task subclasses DecorrelateALKernelTask in order to implement
357  all of that task's configuration parameters, as well as its `run` method.
358  """
359  ConfigClass = DecorrelateALKernelConfig
360  _DefaultName = 'ip_diffim_decorrelateALKernelMapper'
361 
362  def __init__(self, *args, **kwargs):
363  DecorrelateALKernelTask.__init__(self, *args, **kwargs)
364 
365  def run(self, subExposure, expandedSubExposure, fullBBox,
366  template, science, alTaskResult=None, psfMatchingKernel=None,
367  preConvKernel=None, **kwargs):
368  """Perform decorrelation operation on `subExposure`, using
369  `expandedSubExposure` to allow for invalid edge pixels arising from
370  convolutions.
371 
372  This method performs A&L decorrelation on `subExposure` using
373  local measures for image variances and PSF. `subExposure` is a
374  sub-exposure of the non-decorrelated A&L diffim. It also
375  requires the corresponding sub-exposures of the template
376  (`template`) and science (`science`) exposures.
377 
378  Parameters
379  ----------
380  subExposure : lsst.afw.image.Exposure
381  the sub-exposure of the diffim
382  expandedSubExposure : lsst.afw.image.Exposure
383  the expanded sub-exposure upon which to operate
384  fullBBox : afwGeom.BoundingBox
385  the bounding box of the original exposure
386  template : afw.Exposure
387  the corresponding sub-exposure of the template exposure
388  science : afw.Exposure
389  the corresponding sub-exposure of the science exposure
390  alTaskResult : pipeBase.Struct
391  the result of A&L image differencing on `science` and
392  `template`, importantly containing the resulting
393  `psfMatchingKernel`. Can be `None`, only if
394  `psfMatchingKernel` is not `None`.
395  psfMatchingKernel : Alternative parameter for passing the
396  A&L `psfMatchingKernel` directly.
397  preConvKernel : If not None, then pre-filtering was applied
398  to science exposure, and this is the pre-convolution
399  kernel.
400  kwargs :
401  additional keyword arguments propagated from
402  `ImageMapReduceTask.run`.
403 
404  Returns
405  -------
406  A `pipeBase.Struct` containing:
407  * `subExposure` : the result of the `subExposure` processing.
408  * `decorrelationKernel` : the decorrelation kernel, currently
409  not used.
410 
411  Notes
412  -----
413  This `run` method accepts parameters identical to those of
414  `ImageMapperSubtask.run`, since it is called from the
415  `ImageMapperTask`. See that class for more information.
416  """
417  templateExposure = template # input template
418  scienceExposure = science # input science image
419  if alTaskResult is None and psfMatchingKernel is None:
420  raise RuntimeError('Both alTaskResult and psfMatchingKernel cannot be None')
421  psfMatchingKernel = alTaskResult.psfMatchingKernel if alTaskResult is not None else psfMatchingKernel
422 
423  # subExp and expandedSubExp are subimages of the (un-decorrelated) diffim!
424  # So here we compute corresponding subimages of templateExposure and scienceExposure
425  subExp2 = scienceExposure.Factory(scienceExposure, expandedSubExposure.getBBox())
426  subExp1 = templateExposure.Factory(templateExposure, expandedSubExposure.getBBox())
427 
428  # Prevent too much log INFO verbosity from DecorrelateALKernelTask.run
429  logLevel = self.log.getLevel()
430  self.log.setLevel(lsst.log.WARN)
431  res = DecorrelateALKernelTask.run(self, subExp2, subExp1, expandedSubExposure,
432  psfMatchingKernel)
433  self.log.setLevel(logLevel) # reset the log level
434 
435  diffim = res.correctedExposure.Factory(res.correctedExposure, subExposure.getBBox())
436  out = pipeBase.Struct(subExposure=diffim, decorrelationKernel=res.correctionKernel)
437  return out
438 
439 
440 class DecorrelateALKernelMapReduceConfig(ImageMapReduceConfig):
441  """Configuration parameters for the ImageMapReduceTask to direct it to use
442  DecorrelateALKernelMapperSubtask as its mapperSubtask for A&L decorrelation.
443  """
444  mapperSubtask = pexConfig.ConfigurableField(
445  doc='A&L decorrelation subtask to run on each sub-image',
446  target=DecorrelateALKernelMapperSubtask
447  )
448 
449 
450 ## \addtogroup LSST_task_documentation
451 ## \{
452 ## \page DecorrelateALKernelSpatialTask
453 ## \ref DecorrelateALKernelSpatialTask_ "DecorrelateALKernelSpatialTask"
454 ## Decorrelate the effect of convolution by Alard-Lupton matching kernel in image difference,
455 ## allowing for spatial variations in PSF and noise
456 ## \}
457 
458 
459 class DecorrelateALKernelSpatialConfig(pexConfig.Config):
460  """Configuration parameters for the DecorrelateALKernelSpatialTask.
461  """
462  decorrelateConfig = pexConfig.ConfigField(
463  dtype=DecorrelateALKernelConfig,
464  doc='DecorrelateALKernel config to use when running on complete exposure (non spatially-varying)',
465  )
466 
467  decorrelateMapReduceConfig = pexConfig.ConfigField(
468  dtype=DecorrelateALKernelMapReduceConfig,
469  doc='DecorrelateALKernelMapReduce config to use when running on each sub-image (spatially-varying)',
470  )
471 
472  ignoreMaskPlanes = pexConfig.ListField(
473  dtype=str,
474  doc="""Mask planes to ignore for sigma-clipped statistics""",
475  default=("INTRP", "EDGE", "DETECTED", "SAT", "CR", "BAD", "NO_DATA", "DETECTED_NEGATIVE")
476  )
477 
478  def setDefaults(self):
479  self.decorrelateMapReduceConfig.gridStepX = self.decorrelateMapReduceConfig.gridStepY = 19
480  self.decorrelateMapReduceConfig.cellSizeX = self.decorrelateMapReduceConfig.cellSizeY = 20
481  self.decorrelateMapReduceConfig.borderSizeX = self.decorrelateMapReduceConfig.borderSizeY = 6
482  self.decorrelateMapReduceConfig.reducerSubtask.reduceOperation = 'average'
483 
484 
485 class DecorrelateALKernelSpatialTask(pipeBase.Task):
486  """!
487  \anchor DecorrelateALKernelSpatialTask_
488 
489  \brief Decorrelate the effect of convolution by Alard-Lupton matching kernel in image difference
490 
491  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Contents Contents
492 
493  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Purpose
494  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Config
495  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Run
496  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Debug
497  - \ref ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Example
498 
499  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Purpose Description
500 
501  Pipe-task that removes the neighboring-pixel covariance in an
502  image difference that are added when the template image is
503  convolved with the Alard-Lupton PSF matching kernel.
504 
505  This task is a simple wrapper around \ref DecorrelateALKernelTask,
506  which takes a `spatiallyVarying` parameter in its `run` method. If
507  it is `False`, then it simply calls the `run` method of \ref
508  DecorrelateALKernelTask. If it is True, then it uses the \ref
509  ImageMapReduceTask framework to break the exposures into
510  subExposures on a grid, and performs the `run` method of \ref
511  DecorrelateALKernelTask on each subExposure. This enables it to
512  account for spatially-varying PSFs and noise in the exposures when
513  performing the decorrelation.
514 
515  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Initialize Task initialization
516 
517  \copydoc \_\_init\_\_
518 
519  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Run Invoking the Task
520 
521  \copydoc run
522 
523  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Config Configuration parameters
524 
525  See \ref DecorrelateALKernelSpatialConfig
526 
527  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Debug Debug variables
528 
529  This task has no debug variables
530 
531  \section ip_diffim_imageDecorrelation_DecorrelateALKernelSpatialTask_Example Example of using DecorrelateALKernelSpatialTask
532 
533  This task has no standalone example, however it is applied as a
534  subtask of pipe.tasks.imageDifference.ImageDifferenceTask.
535  There is also an example of its use in `tests/testImageDecorrelation.py`.
536  """
537  ConfigClass = DecorrelateALKernelSpatialConfig
538  _DefaultName = "ip_diffim_decorrelateALKernelSpatial"
539 
540  def __init__(self, *args, **kwargs):
541  """Create the image decorrelation Task
542 
543  Parameters
544  ----------
545  args :
546  arguments to be passed to
547  `lsst.pipe.base.task.Task.__init__`
548  kwargs :
549  additional keyword arguments to be passed to
550  `lsst.pipe.base.task.Task.__init__`
551  """
552  pipeBase.Task.__init__(self, *args, **kwargs)
553 
554  self.statsControl = afwMath.StatisticsControl()
555  self.statsControl.setNumSigmaClip(3.)
556  self.statsControl.setNumIter(3)
557  self.statsControl.setAndMask(afwImage.Mask\
558  .getPlaneBitMask(self.config.ignoreMaskPlanes))
559 
560  def computeVarianceMean(self, exposure):
561  """Compute the mean of the variance plane of `exposure`.
562  """
563  statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(),
564  exposure.getMaskedImage().getMask(),
565  afwMath.MEANCLIP, self.statsControl)
566  var = statObj.getValue(afwMath.MEANCLIP)
567  return var
568 
569  def run(self, scienceExposure, templateExposure, subtractedExposure, psfMatchingKernel,
570  spatiallyVarying=True, doPreConvolve=False):
571  """! Perform decorrelation of an image difference exposure.
572 
573  Decorrelates the diffim due to the convolution of the
574  templateExposure with the A&L psfMatchingKernel. If
575  `spatiallyVarying` is True, it utilizes the spatially varying
576  matching kernel via the `imageMapReduce` framework to perform
577  spatially-varying decorrelation on a grid of subExposures.
578 
579  Parameters
580  ----------
581  scienceExposure : lsst.afw.image.Exposure
582  the science Exposure used for PSF matching
583  templateExposure : lsst.afw.image.Exposure
584  the template Exposure used for PSF matching
585  subtractedExposure : lsst.afw.image.Exposure
586  the subtracted Exposure produced by `ip_diffim.ImagePsfMatchTask.subtractExposures()`
587  psfMatchingKernel :
588  an (optionally spatially-varying) PSF matching kernel produced
589  by `ip_diffim.ImagePsfMatchTask.subtractExposures()`
590  spatiallyVarying : bool
591  if True, perform the spatially-varying operation
592  doPreConvolve : bool
593  if True, the scienceExposure has been pre-filtered with its PSF. (Currently
594  this option is experimental.)
595 
596  Returns
597  -------
598  a `pipeBase.Struct` containing:
599  * `correctedExposure`: the decorrelated diffim
600  """
601  self.log.info('Running A&L decorrelation: spatiallyVarying=%r' % spatiallyVarying)
602 
603  svar = self.computeVarianceMean(scienceExposure)
604  tvar = self.computeVarianceMean(templateExposure)
605 
606  var = self.computeVarianceMean(subtractedExposure)
607 
608  if spatiallyVarying:
609  self.log.info("Variance (science, template): (%f, %f)", svar, tvar)
610  self.log.info("Variance (uncorrected diffim): %f", var)
611  config = self.config.decorrelateMapReduceConfig
612  task = ImageMapReduceTask(config=config)
613  results = task.run(subtractedExposure, science=scienceExposure,
614  template=templateExposure, psfMatchingKernel=psfMatchingKernel,
615  preConvKernel=None, forceEvenSized=True)
616  results.correctedExposure = results.exposure
617 
618  # Make sure masks of input image are propagated to diffim
619  def gm(exp):
620  return exp.getMaskedImage().getMask()
621  gm(results.correctedExposure)[:, :] = gm(subtractedExposure)
622 
623  var = self.computeVarianceMean(results.correctedExposure)
624  self.log.info("Variance (corrected diffim): %f", var)
625 
626  else:
627  config = self.config.decorrelateConfig
628  task = DecorrelateALKernelTask(config=config)
629  results = task.run(scienceExposure, templateExposure,
630  subtractedExposure, psfMatchingKernel)
631 
632  return results
def _fixOddKernel
Take a kernel with odd dimensions and make them even for FFT.
def run
Perform decorrelation of an image difference exposure.
def computeCorrectedDiffimPsf
Compute the (decorrelated) difference image&#39;s new PSF.
def run
Perform decorrelation of an image difference exposure.
Decorrelate the effect of convolution by Alard-Lupton matching kernel in image difference.
def _fixEvenKernel
Take a kernel with even dimensions and make them odd, centered correctly.
Decorrelate the effect of convolution by Alard-Lupton matching kernel in image difference.
Configuration parameters for the DecorrelateALKernelTask.
def _doConvolve
Convolve an Exposure with a decorrelation convolution kernel.
def _computeDecorrelationKernel
Compute the Lupton/ZOGY post-conv.