1 from __future__
import absolute_import, division, print_function
2 from future
import standard_library
3 standard_library.install_aliases()
28 import lsst.afw.image
as afwImage
29 import lsst.afw.geom
as afwGeom
30 import lsst.meas.algorithms
as measAlg
31 import lsst.afw.math
as afwMath
32 import lsst.pex.config
as pexConfig
33 import lsst.pipe.base
as pipeBase
36 from .imageMapReduce
import (ImageMapReduceConfig, ImageMapperSubtask,
37 ImageMapperSubtaskConfig)
39 __all__ = [
"ZogyTask",
"ZogyConfig",
40 "ZogyMapperSubtask",
"ZogyMapReduceConfig"]
43 """Tasks for performing the "Proper image subtraction" algorithm of
44 Zackay, et al. (2016), hereafter simply referred to as 'ZOGY (2016)'.
46 `ZogyTask` contains methods to perform the basic estimation of the
47 ZOGY diffim `D`, its updated PSF, and the variance-normalized
48 likelihood image `S_corr`. We have implemented ZOGY using the
49 proscribed methodology, computing all convolutions in Fourier space,
50 and also variants in which the convolutions are performed in real
51 (image) space. The former is faster and results in fewer artifacts
52 when the PSFs are noisy (i.e., measured, for example, via
53 `PsfEx`). The latter is presumed to be preferred as it can account for
54 masks correctly with fewer "ringing" artifacts from edge effects or
55 saturated stars, but noisy PSFs result in their own smaller
56 artifacts. Removal of these artifacts is a subject of continuing
57 research. Currently, we "pad" the PSFs when performing the
58 subtractions in real space, which reduces, but does not entirely
59 eliminate these artifacts.
61 All methods in `ZogyTask` assume template and science images are
62 already accurately photometrically and astrometrically registered.
64 `ZogyMapperSubtask` is a wrapper which runs `ZogyTask` in the
65 `ImageMapReduce` framework, computing of ZOGY diffim's on small,
66 overlapping sub-images, thereby enabling complete ZOGY diffim's which
67 account for spatially-varying noise and PSFs across the two input
68 exposures. An example of the use of this task is in the `testZogy.py`
74 """Configuration parameters for the ZogyTask
76 inImageSpace = pexConfig.Field(
79 doc=
"""Perform all convolutions in real (image) space rather than Fourier space.
80 Currently if True, this results in artifacts when using real (noisy) PSFs."""
83 padSize = pexConfig.Field(
86 doc=
"""Number of pixels to pad PSFs to avoid artifacts (when inImageSpace is True)"""
89 templateFluxScaling = pexConfig.Field(
92 doc=
"""Template flux scaling factor (Fr in ZOGY paper)"""
95 scienceFluxScaling = pexConfig.Field(
98 doc=
"""Science flux scaling factor (Fn in ZOGY paper)"""
101 doTrimKernels = pexConfig.Field(
104 doc=
"""Trim kernels for image-space ZOGY. Speeds up convolutions and shrinks artifacts.
105 Subject of future research."""
108 doFilterPsfs = pexConfig.Field(
111 doc=
"""Filter PSFs for image-space ZOGY. Aids in reducing artifacts.
112 Subject of future research."""
115 ignoreMaskPlanes = pexConfig.ListField(
117 default=(
"INTRP",
"EDGE",
"DETECTED",
"SAT",
"CR",
"BAD",
"NO_DATA",
"DETECTED_NEGATIVE"),
118 doc=
"""Mask planes to ignore for statistics"""
123 """Task to perform ZOGY proper image subtraction. See module-level documentation for
126 In all methods, im1 is R (reference, or template) and im2 is N (new, or science).
128 ConfigClass = ZogyConfig
129 _DefaultName =
"ip_diffim_Zogy"
131 def __init__(self, templateExposure, scienceExposure, sig1=None, sig2=None,
132 psf1=
None, psf2=
None, *args, **kwargs):
133 """Create the ZOGY task.
137 templateExposure : lsst.afw.image.Exposure
138 Template exposure ("Reference image" in ZOGY (2016)).
139 scienceExposure : lsst.afw.image.Exposure
140 Science exposure ("New image" in ZOGY (2016)). Must have already been
141 registered and photmetrically matched to template.
143 (Optional) sqrt(variance) of `templateExposure`. If `None`, it is
144 computed from the sqrt(mean) of the `templateExposure` variance image.
146 (Optional) sqrt(variance) of `scienceExposure`. If `None`, it is
147 computed from the sqrt(mean) of the `scienceExposure` variance image.
148 psf1 : 2D numpy.array
149 (Optional) 2D array containing the PSF image for the template. If
150 `None`, it is extracted from the PSF taken at the center of `templateExposure`.
151 psf2 : 2D numpy.array
152 (Optional) 2D array containing the PSF image for the science img. If
153 `None`, it is extracted from the PSF taken at the center of `scienceExposure`.
155 additional arguments to be passed to
156 `lsst.pipe.base.task.Task.__init__`
158 additional keyword arguments to be passed to
159 `lsst.pipe.base.task.Task.__init__`
161 pipeBase.Task.__init__(self, *args, **kwargs)
167 self.statsControl.setNumSigmaClip(3.)
168 self.statsControl.setNumIter(3)
169 self.statsControl.setAndMask(afwImage.MaskU.getPlaneBitMask(self.config.ignoreMaskPlanes))
171 self.
im1 = self.template.getMaskedImage().getImage().getArray()
172 self.
im2 = self.science.getMaskedImage().getImage().getArray()
173 self.
im1_var = self.template.getMaskedImage().getVariance().getArray()
174 self.
im2_var = self.science.getMaskedImage().getVariance().getArray()
176 def selectPsf(psf, exposure):
180 bbox1 = self.template.getBBox()
181 xcen = (bbox1.getBeginX() + bbox1.getEndX()) / 2.
182 ycen = (bbox1.getBeginY() + bbox1.getEndY()) / 2.
183 return exposure.getPsf().computeKernelImage(afwGeom.Point2D(xcen, ycen)).getArray()
189 if self.im1_psf.shape[0] < self.im2_psf.shape[0]:
190 self.
im1_psf = np.pad(self.
im1_psf, (((self.im2_psf.shape[0] - self.im1_psf.shape[0])//2,
191 (self.im2_psf.shape[1] - self.im1_psf.shape[1])//2)),
192 mode=
'constant', constant_values=0)
193 elif self.im2_psf.shape[0] < self.im1_psf.shape[0]:
194 self.
im2_psf = np.pad(self.
im2_psf, (((self.im1_psf.shape[0] - self.im2_psf.shape[0])//2,
195 (self.im1_psf.shape[1] - self.im2_psf.shape[1])//2)),
196 mode=
'constant', constant_values=0)
201 self.
Fr = self.config.templateFluxScaling
202 self.
Fn = self.config.scienceFluxScaling
206 """Compute the sigma-clipped mean of the variance image of `exposure`.
208 statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(),
209 exposure.getMaskedImage().getMask(),
211 var = statObj.getValue(afwMath.MEANCLIP)
216 """Zero-pad `psf` to the dimensions given by `size`.
221 Input psf to be padded
223 Two element list containing the dimensions to pad the `psf` to
228 The padded copy of the input `psf`.
233 if padSize0 > 0
or padSize1 > 0:
238 psf = np.pad(psf, ((padSize0, padSize0-1), (padSize1, padSize1-1)), mode=
'constant',
244 """Zero-pad `psf` to same dimensions as im.
249 Input psf to be padded
250 im : lsst.afw.Image, MaskedImage or Exposure
251 Dimensions of this image are used to set the PSF pad dimensions
256 The padded copy of the input `psf`.
258 return ZogyTask._padPsfToSize(psf, (im.shape[0]//2 - psf.shape[0]//2,
259 im.shape[1]//2 - psf.shape[1]//2))
262 """Compute standard ZOGY quantities used by (nearly) all methods.
264 Many of the ZOGY calculations require similar quantities, including
265 FFTs of the PSFs, and the "denominator" term (e.g. in eq. 13 of
266 ZOGY manuscript (2016). This function consolidates many of those
271 psf1 : 2D numpy.array
272 (Optional) Input psf of template, override if already padded
273 psf2 : 2D numpy.array
274 (Optional) Input psf of science image, override if already padded
278 A lsst.pipe.base.Struct containing:
279 - Pr : 2D numpy.array, the (possibly zero-padded) template PSF
280 - Pn : 2D numpy.array, the (possibly zero-padded) science PSF
281 - Pr_hat : 2D numpy.array, the FFT of `Pr`
282 - Pn_hat : 2D numpy.array, the FFT of `Pn`
283 - denom : 2D numpy.array, the denominator of equation (13) in ZOGY (2016) manuscript
284 - Fd : float, the relative flux scaling factor between science and template
286 psf1 = self.
im1_psf if psf1
is None else psf1
287 psf2 = self.
im2_psf if psf2
is None else psf2
288 padSize = self.
padSize if padSize
is None else padSize
291 Pr = ZogyTask._padPsfToSize(psf1, (padSize, padSize))
292 Pn = ZogyTask._padPsfToSize(psf2, (padSize, padSize))
295 Pr_hat = np.fft.fft2(Pr)
296 Pr_hat2 = np.conj(Pr_hat) * Pr_hat
297 Pn_hat = np.fft.fft2(Pn)
298 Pn_hat2 = np.conj(Pn_hat) * Pn_hat
299 denom = np.sqrt((sigN**2 * self.
Fr**2 * Pr_hat2) + (sigR**2 * self.
Fn**2 * Pn_hat2))
300 Fd = self.
Fr*self.
Fn / np.sqrt(sigN**2 * self.
Fr**2 + sigR**2 * self.
Fn**2)
302 res = pipeBase.Struct(
303 Pr=Pr, Pn=Pn, Pr_hat=Pr_hat, Pn_hat=Pn_hat, denom=denom, Fd=Fd
309 """Compute ZOGY diffim `D` as proscribed in ZOGY (2016) manuscript
311 Compute the ZOGY eqn. (13):
313 \widehat{D} = \frac{Fr\widehat{Pr}\widehat{N} -
314 F_n\widehat{Pn}\widehat{R}}{\sqrt{\sigma_n^2 Fr^2
315 |\widehat{Pr}|^2 + \sigma_r^2 F_n^2 |\widehat{Pn}|^2}}
317 where $D$ is the optimal difference image, $R$ and $N$ are the
318 reference and "new" image, respectively, $Pr$ and $P_n$ are their
319 PSFs, $Fr$ and $Fn$ are their flux-based zero-points (which we
320 will set to one here), $\sigma_r^2$ and $\sigma_n^2$ are their
321 variance, and $\widehat{D}$ denotes the FT of $D$.
325 A lsst.pipe.base.Struct containing:
326 - D : 2D numpy.array, the proper image difference
327 - D_var : 2D numpy.array, the variance image for `D`
330 psf1 = ZogyTask._padPsfToImageSize(self.
im1_psf, self.
im1)
331 psf2 = ZogyTask._padPsfToImageSize(self.
im2_psf, self.
im2)
335 def _filterKernel(K, trim_amount):
338 K[:ps, :] = K[-ps:, :] = 0
339 K[:, :ps] = K[:, -ps:] = 0
342 Kr_hat = self.
Fr * preqs.Pr_hat / preqs.denom
343 Kn_hat = self.
Fn * preqs.Pn_hat / preqs.denom
344 if debug
and self.config.doTrimKernels:
347 ps = (Kn_hat.shape[1] - 80)//2
348 Kn = _filterKernel(np.fft.ifft2(Kn_hat), ps)
349 Kn_hat = np.fft.fft2(Kn)
350 Kr = _filterKernel(np.fft.ifft2(Kr_hat), ps)
351 Kr_hat = np.fft.fft2(Kr)
353 def processImages(im1, im2, doAdd=False):
355 im1[np.isinf(im1)] = np.nan
356 im1[np.isnan(im1)] = np.nanmean(im1)
357 im2[np.isinf(im2)] = np.nan
358 im2[np.isnan(im2)] = np.nanmean(im2)
360 R_hat = np.fft.fft2(im1)
361 N_hat = np.fft.fft2(im2)
363 D_hat = Kr_hat * N_hat
365 D_hat -= Kn_hat * R_hat
367 D_hat += Kn_hat * R_hat
369 D = np.fft.ifft2(D_hat)
370 D = np.fft.ifftshift(D.real) / preqs.Fd
374 D = processImages(self.
im1, self.
im2, doAdd=
False)
378 return pipeBase.Struct(D=D, D_var=D_var)
381 """! Convolve an Exposure with a decorrelation convolution kernel.
385 exposure : lsst.afw.image.Exposure to be convolved.
386 kernel : 2D numpy.array to convolve the image with
390 A new lsst.afw.image.Exposure with the convolved pixels and the (possibly
395 - We optionally re-center the kernel if necessary and return the possibly
398 kernelImg = afwImage.ImageD(kernel.shape[0], kernel.shape[1])
399 kernelImg.getArray()[:, :] = kernel
400 kern = afwMath.FixedKernel(kernelImg)
402 maxloc = np.unravel_index(np.argmax(kernel), kernel.shape)
403 kern.setCtrX(maxloc[0])
404 kern.setCtrY(maxloc[1])
405 outExp = exposure.clone()
406 convCntrl = afwMath.ConvolutionControl(doNormalize=
False, doCopyEdge=
False,
407 maxInterpolationDistance=0)
409 afwMath.convolve(outExp.getMaskedImage(), exposure.getMaskedImage(), kern, convCntrl)
412 afwMath.convolve(outExp, exposure, kern, convCntrl)
417 """Compute ZOGY diffim `D` using image-space convlutions
419 This method is still being debugged as it results in artifacts
420 when the PSFs are noisy (see module-level docstring). Thus
421 there are several options still enabled by the `debug` flag,
422 which are disabled by defult.
426 padSize : int, the amount to pad the PSFs by
427 debug : bool, flag to enable debugging tests and options
431 D : lsst.afw.Exposure
432 the proper image difference, including correct variance,
440 Kr_hat = (preqs.Pr_hat + delta) / (preqs.denom + delta)
441 Kn_hat = (preqs.Pn_hat + delta) / (preqs.denom + delta)
442 Kr = np.fft.ifft2(Kr_hat).real
443 Kr = np.roll(np.roll(Kr, -1, 0), -1, 1)
444 Kn = np.fft.ifft2(Kn_hat).real
445 Kn = np.roll(np.roll(Kn, -1, 0), -1, 1)
447 def _trimKernel(self, K, trim_amount):
451 K = K[ps:-ps, ps:-ps]
454 padSize = self.
padSize if padSize
is None else padSize
456 if debug
and self.config.doTrimKernels:
459 Kn = _trimKernel(Kn, padSize)
460 Kr = _trimKernel(Kr, padSize)
466 tmp = D.getMaskedImage()
467 tmp -= exp1.getMaskedImage()
472 """Utility method to set an exposure's PSF when provided as a 2-d numpy.array
474 psfI = afwImage.ImageD(psfArr.shape[0], psfArr.shape[1])
475 psfI.getArray()[:, :] = psfArr
476 psfK = afwMath.FixedKernel(psfI)
477 psfNew = measAlg.KernelPsf(psfK)
478 exposure.setPsf(psfNew)
482 """Wrapper method to compute ZOGY proper diffim
484 This method should be used as the public interface for
485 computing the ZOGY diffim.
490 Override config `inImageSpace` parameter
492 Override config `padSize` parameter
494 additional keyword arguments to be passed to
495 `computeDiffimFourierSpace` or `computeDiffimImageSpace`.
499 D : lsst.afw.Exposure
500 the proper image difference, including correct variance,
503 inImageSpace = self.config.inImageSpace
if inImageSpace
is None else inImageSpace
505 padSize = self.
padSize if padSize
is None else padSize
509 D = self.science.clone()
510 D.getMaskedImage().getImage().getArray()[:, :] = res.D
511 D.getMaskedImage().getVariance().getArray()[:, :] = res.D_var
518 """Compute the ZOGY diffim PSF (ZOGY manuscript eq. 14)
523 Override config `padSize` parameter
525 Return the FFT of the diffim PSF (do not inverse-FFT it)
526 psf1 : 2D numpy.array
527 (Optional) Input psf of template, override if already padded
528 psf2 : 2D numpy.array
529 (Optional) Input psf of science image, override if already padded
533 Pd : 2D numpy.array, the diffim PSF (or FFT of PSF if `keepFourier=True`)
535 preqs = self.
computePrereqs(psf1=psf1, psf2=psf2, padSize=padSize)
537 Pd_hat_numerator = (self.
Fr * self.
Fn * preqs.Pr_hat * preqs.Pn_hat)
538 Pd_hat = Pd_hat_numerator / (preqs.Fd * preqs.denom)
543 Pd = np.fft.ifft2(Pd_hat)
544 Pd = np.fft.ifftshift(Pd).real
549 R_hat=
None, Kr_hat=
None, Kr=
None,
550 N_hat=
None, Kn_hat=
None, Kn=
None):
551 """Compute the astrometric noise correction terms
553 Compute the correction for estimated astrometric noise as
554 proscribed in ZOGY (2016), section 3.3. All convolutions
555 performed either in real (image) or Fourier space.
559 xVarAst, yVarAst : float
560 estimated astrometric noise (variance of astrometric registration errors)
562 Perform all convolutions in real (image) space rather than Fourier space
563 R_hat : 2-D numpy.array
564 (Optional) FFT of template image, only required if `inImageSpace=False`
565 Kr_hat : 2-D numpy.array
566 FFT of Kr kernel (eq. 28 of ZOGY (2016)), only required if `inImageSpace=False`
568 Kr kernel (eq. 28 of ZOGY (2016)), only required if `inImageSpace=True`.
569 Kr is associated with the template (reference).
570 N_hat : 2-D numpy.array
571 FFT of science image, only required if `inImageSpace=False`
572 Kn_hat : 2-D numpy.array
573 FFT of Kn kernel (eq. 29 of ZOGY (2016)), only required if `inImageSpace=False`
575 Kn kernel (eq. 29 of ZOGY (2016)), only required if `inImageSpace=True`.
576 Kn is associated with the science (new) image.
580 VastSR, VastSN : 2-D numpy.arrays containing the values in eqs. 30 and 32 of
584 if xVarAst + yVarAst > 0:
587 S_R = S_R.getMaskedImage().getImage().getArray()
589 S_R = np.fft.ifft2(R_hat * Kr_hat)
590 gradRx, gradRy = np.gradient(S_R)
591 VastSR = xVarAst * gradRx**2. + yVarAst * gradRy**2.
595 S_N = S_N.getMaskedImage().getImage().getArray()
597 S_N = np.fft.ifft2(N_hat * Kn_hat)
598 gradNx, gradNy = np.gradient(S_N)
599 VastSN = xVarAst * gradNx**2. + yVarAst * gradNy**2.
601 return VastSR, VastSN
604 """Compute corrected likelihood image, optimal for source detection
606 Compute ZOGY S_corr image. This image can be thresholded for
607 detection without optimal filtering, and the variance image is
608 corrected to account for astrometric noise (errors in
609 astrometric registration whether systematic or due to effects
610 such as DCR). The calculations here are all performed in
611 Fourier space, as proscribed in ZOGY (2016).
615 xVarAst, yVarAst : float
616 estimated astrometric noise (variance of astrometric registration errors)
620 A lsst.pipe.base.Struct containing:
621 - S : numpy.array, the likelihood image S (eq. 12 of ZOGY (2016))
622 - S_var : the corrected variance image (denominator of eq. 25 of ZOGY (2016))
623 - Dpsf : the PSF of the diffim D, likely never to be used.
626 psf1 = ZogyTask._padPsfToImageSize(self.
im1_psf, self.
im1)
627 psf2 = ZogyTask._padPsfToImageSize(self.
im2_psf, self.
im2)
632 R_hat = np.fft.fft2(self.
im1)
633 N_hat = np.fft.fft2(self.
im2)
634 D_hat = self.
Fr * preqs.Pr_hat * N_hat - self.
Fn * preqs.Pn_hat * R_hat
637 Pd_hat = self.
computeDiffimPsf(padSize=0, keepFourier=
True, psf1=psf1, psf2=psf2)
638 Pd_bar = np.conj(Pd_hat)
639 S = np.fft.ifft2(D_hat * Pd_bar)
643 Pn_hat2 = np.conj(preqs.Pn_hat) * preqs.Pn_hat
644 Kr_hat = self.
Fr * self.
Fn**2. * np.conj(preqs.Pr_hat) * Pn_hat2 / preqs.denom**2.
645 Pr_hat2 = np.conj(preqs.Pr_hat) * preqs.Pr_hat
646 Kn_hat = self.
Fn * self.
Fr**2. * np.conj(preqs.Pn_hat) * Pr_hat2 / preqs.denom**2.
648 Kr_hat2 = np.fft.fft2(np.fft.ifft2(Kr_hat)**2.)
649 Kn_hat2 = np.fft.fft2(np.fft.ifft2(Kn_hat)**2.)
650 var1c_hat = Kr_hat2 * np.fft.fft2(self.
im1_var)
651 var2c_hat = Kn_hat2 * np.fft.fft2(self.
im2_var)
655 R_hat=R_hat, Kr_hat=Kr_hat,
656 N_hat=N_hat, Kn_hat=Kn_hat)
658 S_var = np.sqrt(np.fft.ifftshift(np.fft.ifft2(var1c_hat + var2c_hat)) + fGradR + fGradN)
661 S = np.fft.ifftshift(np.fft.ifft2(Kn_hat * N_hat - Kr_hat * R_hat))
665 return pipeBase.Struct(S=S.real, S_var=S_var.real, Dpsf=Pd)
668 """Compute corrected likelihood image, optimal for source detection
670 Compute ZOGY S_corr image. This image can be thresholded for
671 detection without optimal filtering, and the variance image is
672 corrected to account for astrometric noise (errors in
673 astrometric registration whether systematic or due to effects
674 such as DCR). The calculations here are all performed in
679 xVarAst, yVarAst : float
680 estimated astrometric noise (variance of astrometric registration errors)
685 - S : lsst.afw.image.Exposure, the likelihood exposure S (eq. 12 of ZOGY (2016)),
686 including corrected variance, masks, and PSF
687 - D : lsst.afw.Exposure, the proper image difference, including correct
688 variance, masks, and PSF
693 padSize = self.
padSize if padSize
is None else padSize
697 Pd_bar = np.fliplr(np.flipud(Pd))
699 tmp = S.getMaskedImage()
704 Pn_hat2 = np.conj(preqs.Pn_hat) * preqs.Pn_hat
705 Kr_hat = self.
Fr * self.
Fn**2. * np.conj(preqs.Pr_hat) * Pn_hat2 / preqs.denom**2.
706 Pr_hat2 = np.conj(preqs.Pr_hat) * preqs.Pr_hat
707 Kn_hat = self.
Fn * self.
Fr**2. * np.conj(preqs.Pn_hat) * Pr_hat2 / preqs.denom**2.
709 Kr = np.fft.ifft2(Kr_hat).real
710 Kr = np.roll(np.roll(Kr, -1, 0), -1, 1)
711 Kn = np.fft.ifft2(Kn_hat).real
712 Kn = np.roll(np.roll(Kn, -1, 0), -1, 1)
713 var1c, _ = self.
_doConvolve(self.template.getMaskedImage().getVariance(), Kr**2.)
714 var2c, _ = self.
_doConvolve(self.science.getMaskedImage().getVariance(), Kn**2.)
720 Smi = S.getMaskedImage()
722 S_var = np.sqrt(var1c.getArray() + var2c.getArray() + fGradR + fGradN)
723 S.getMaskedImage().getVariance().getArray()[:, :] = S_var
728 def computeScorr(self, xVarAst=0., yVarAst=0., inImageSpace=None, padSize=0, **kwargs):
729 """Wrapper method to compute ZOGY corrected likelihood image, optimal for
732 This method should be used as the public interface for
733 computing the ZOGY S_corr.
737 xVarAst, yVarAst : float
738 estimated astrometric noise (variance of astrometric registration errors)
740 Override config `inImageSpace` parameter
742 Override config `padSize` parameter
746 S : lsst.afw.image.Exposure, the likelihood exposure S (eq. 12 of ZOGY (2016)),
747 including corrected variance, masks, and PSF
749 inImageSpace = self.config.inImageSpace
if inImageSpace
is None else inImageSpace
755 S = self.science.clone()
756 S.getMaskedImage().getImage().getArray()[:, :] = res.S
757 S.getMaskedImage().getVariance().getArray()[:, :] = res.S_var
764 """Task to be used as an ImageMapperSubtask for performing
765 ZOGY image subtraction on a grid of subimages.
767 ConfigClass = ZogyConfig
768 _DefaultName =
'ip_diffim_ZogyMapper'
771 ImageMapperSubtask.__init__(self, *args, **kwargs)
773 def run(self, subExposure, expandedSubExposure, fullBBox, template,
775 """Perform ZOGY proper image subtraction on sub-images
777 This method performs ZOGY proper image subtraction on
778 `subExposure` using local measures for image variances and
779 PSF. `subExposure` is a sub-exposure of the science image. It
780 also requires the corresponding sub-exposures of the template
781 (`template`). The operations are actually performed on
782 `expandedSubExposure` to allow for invalid edge pixels arising
783 from convolutions, which are then removed.
787 subExposure : lsst.afw.image.Exposure
788 the sub-exposure of the diffim
789 expandedSubExposure : lsst.afw.image.Exposure
790 the expanded sub-exposure upon which to operate
791 fullBBox : lsst.afw.geom.BoundingBox
792 the bounding box of the original exposure
793 template : lsst.afw.image.Exposure
794 the template exposure, from which a corresponding sub-exposure
797 additional keyword arguments propagated from
798 `ImageMapReduceTask.run`. These include:
800 Compute and return the corrected likelihood image S_corr
801 rather than the proper image difference
802 - inImageSpace : bool
803 Perform all convolutions in real (image) space rather than
804 in Fourier space. This option currently leads to artifacts
805 when using real (measured and noisy) PSFs, thus it is set
806 to `False` by default.
807 These kwargs may also include arguments to be propagated to
808 `ZogyTask.computeDiffim` and `ZogyTask.computeScorr`.
812 A `lsst.pipe.base.Struct` containing the result of the
813 `subExposure` processing, labelled 'subExposure'. In this case
814 it is either the subExposure of the proper image difference D,
815 or (if `doScorr==True`) the corrected likelihood exposure `S`.
819 This `run` method accepts parameters identical to those of
820 `ImageMapperSubtask.run`, since it is called from the
821 `ImageMapperTask`. See that class for more information.
823 bbox = subExposure.getBBox()
824 center = ((bbox.getBeginX() + bbox.getEndX()) // 2., (bbox.getBeginY() + bbox.getEndY()) // 2.)
825 center = afwGeom.Point2D(center[0], center[1])
827 imageSpace = kwargs.pop(
'inImageSpace',
False)
828 doScorr = kwargs.pop(
'doScorr',
False)
829 sigmas = kwargs.pop(
'sigmas',
None)
830 padSize = kwargs.pop(
'padSize', 7)
833 subExp2 = expandedSubExposure
836 subExp1 = template.Factory(template, expandedSubExposure.getBBox())
842 sig1, sig2 = sigmas[0], sigmas[1]
844 def _makePsfSquare(psf):
845 if psf.shape[0] < psf.shape[1]:
847 psf = np.pad(psf, ((1, 1), (0, 0)), mode=
'constant')
848 elif psf.shape[0] > psf.shape[1]:
849 psf = np.pad(psf, ((0, 0), (1, 1)), mode=
'constant')
852 psf2 = subExp2.getPsf().computeKernelImage(center).getArray()
853 psf2 = _makePsfSquare(psf2)
855 psf1 = template.getPsf().computeKernelImage(center).getArray()
856 psf1 = _makePsfSquare(psf1)
859 if subExp1.getDimensions()[0] < psf1.shape[0]
or subExp1.getDimensions()[1] < psf1.shape[1]:
860 return pipeBase.Struct(subExposure=subExposure)
863 """Filter a noisy Psf to remove artifacts. Subject of future research."""
865 if psf.shape[0] == 41:
868 psf[0:10, :] = psf[:, 0:10] = psf[31:41, :] = psf[:, 31:41] = 0
874 if self.config.doFilterPsfs:
876 psf1b = _filterPsf(psf1)
877 psf2b = _filterPsf(psf2)
880 if imageSpace
is True:
881 config.inImageSpace = imageSpace
882 config.padSize = padSize
883 task =
ZogyTask(templateExposure=subExp1, scienceExposure=subExp2,
884 sig1=sig1, sig2=sig2, psf1=psf1b, psf2=psf2b, config=config)
887 D = task.computeDiffim(**kwargs)
889 D = task.computeScorr(**kwargs)
891 outExp = D.Factory(D, subExposure.getBBox())
892 out = pipeBase.Struct(subExposure=outExp)
897 mapperSubtask = pexConfig.ConfigurableField(
898 doc=
'Zogy subtask to run on each sub-image',
899 target=ZogyMapperSubtask
def _doConvolve
Convolve an Exposure with a decorrelation convolution kernel.
def computeDiffimImageSpace
def _computeVarAstGradients
def computeDiffimFourierSpace
def computeScorrImageSpace
def computeScorrFourierSpace