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.Mask\
170 .getPlaneBitMask(self.config.ignoreMaskPlanes))
172 self.
im1 = self.template.getMaskedImage().getImage().getArray()
173 self.
im2 = self.science.getMaskedImage().getImage().getArray()
174 self.
im1_var = self.template.getMaskedImage().getVariance().getArray()
175 self.
im2_var = self.science.getMaskedImage().getVariance().getArray()
177 def selectPsf(psf, exposure):
181 bbox1 = self.template.getBBox()
182 xcen = (bbox1.getBeginX() + bbox1.getEndX()) / 2.
183 ycen = (bbox1.getBeginY() + bbox1.getEndY()) / 2.
184 return exposure.getPsf().computeKernelImage(afwGeom.Point2D(xcen, ycen)).getArray()
190 if self.im1_psf.shape[0] < self.im2_psf.shape[0]:
191 self.
im1_psf = np.pad(self.
im1_psf, (((self.im2_psf.shape[0] - self.im1_psf.shape[0])//2,
192 (self.im2_psf.shape[1] - self.im1_psf.shape[1])//2)),
193 mode=
'constant', constant_values=0)
194 elif self.im2_psf.shape[0] < self.im1_psf.shape[0]:
195 self.
im2_psf = np.pad(self.
im2_psf, (((self.im1_psf.shape[0] - self.im2_psf.shape[0])//2,
196 (self.im1_psf.shape[1] - self.im2_psf.shape[1])//2)),
197 mode=
'constant', constant_values=0)
202 self.
Fr = self.config.templateFluxScaling
203 self.
Fn = self.config.scienceFluxScaling
207 """Compute the sigma-clipped mean of the variance image of `exposure`.
209 statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(),
210 exposure.getMaskedImage().getMask(),
212 var = statObj.getValue(afwMath.MEANCLIP)
217 """Zero-pad `psf` to the dimensions given by `size`.
222 Input psf to be padded
224 Two element list containing the dimensions to pad the `psf` to
229 The padded copy of the input `psf`.
234 if padSize0 > 0
or padSize1 > 0:
239 psf = np.pad(psf, ((padSize0, padSize0-1), (padSize1, padSize1-1)), mode=
'constant',
245 """Zero-pad `psf` to same dimensions as im.
250 Input psf to be padded
251 im : lsst.afw.Image, MaskedImage or Exposure
252 Dimensions of this image are used to set the PSF pad dimensions
257 The padded copy of the input `psf`.
259 return ZogyTask._padPsfToSize(psf, (im.shape[0]//2 - psf.shape[0]//2,
260 im.shape[1]//2 - psf.shape[1]//2))
263 """Compute standard ZOGY quantities used by (nearly) all methods.
265 Many of the ZOGY calculations require similar quantities, including
266 FFTs of the PSFs, and the "denominator" term (e.g. in eq. 13 of
267 ZOGY manuscript (2016). This function consolidates many of those
272 psf1 : 2D numpy.array
273 (Optional) Input psf of template, override if already padded
274 psf2 : 2D numpy.array
275 (Optional) Input psf of science image, override if already padded
279 A lsst.pipe.base.Struct containing:
280 - Pr : 2D numpy.array, the (possibly zero-padded) template PSF
281 - Pn : 2D numpy.array, the (possibly zero-padded) science PSF
282 - Pr_hat : 2D numpy.array, the FFT of `Pr`
283 - Pn_hat : 2D numpy.array, the FFT of `Pn`
284 - denom : 2D numpy.array, the denominator of equation (13) in ZOGY (2016) manuscript
285 - Fd : float, the relative flux scaling factor between science and template
287 psf1 = self.
im1_psf if psf1
is None else psf1
288 psf2 = self.
im2_psf if psf2
is None else psf2
289 padSize = self.
padSize if padSize
is None else padSize
292 Pr = ZogyTask._padPsfToSize(psf1, (padSize, padSize))
293 Pn = ZogyTask._padPsfToSize(psf2, (padSize, padSize))
296 Pr_hat = np.fft.fft2(Pr)
297 Pr_hat2 = np.conj(Pr_hat) * Pr_hat
298 Pn_hat = np.fft.fft2(Pn)
299 Pn_hat2 = np.conj(Pn_hat) * Pn_hat
300 denom = np.sqrt((sigN**2 * self.
Fr**2 * Pr_hat2) + (sigR**2 * self.
Fn**2 * Pn_hat2))
301 Fd = self.
Fr*self.
Fn / np.sqrt(sigN**2 * self.
Fr**2 + sigR**2 * self.
Fn**2)
303 res = pipeBase.Struct(
304 Pr=Pr, Pn=Pn, Pr_hat=Pr_hat, Pn_hat=Pn_hat, denom=denom, Fd=Fd
310 """Compute ZOGY diffim `D` as proscribed in ZOGY (2016) manuscript
312 Compute the ZOGY eqn. (13):
314 \widehat{D} = \frac{Fr\widehat{Pr}\widehat{N} -
315 F_n\widehat{Pn}\widehat{R}}{\sqrt{\sigma_n^2 Fr^2
316 |\widehat{Pr}|^2 + \sigma_r^2 F_n^2 |\widehat{Pn}|^2}}
318 where $D$ is the optimal difference image, $R$ and $N$ are the
319 reference and "new" image, respectively, $Pr$ and $P_n$ are their
320 PSFs, $Fr$ and $Fn$ are their flux-based zero-points (which we
321 will set to one here), $\sigma_r^2$ and $\sigma_n^2$ are their
322 variance, and $\widehat{D}$ denotes the FT of $D$.
326 A lsst.pipe.base.Struct containing:
327 - D : 2D numpy.array, the proper image difference
328 - D_var : 2D numpy.array, the variance image for `D`
331 psf1 = ZogyTask._padPsfToImageSize(self.
im1_psf, self.
im1)
332 psf2 = ZogyTask._padPsfToImageSize(self.
im2_psf, self.
im2)
336 def _filterKernel(K, trim_amount):
339 K[:ps, :] = K[-ps:, :] = 0
340 K[:, :ps] = K[:, -ps:] = 0
343 Kr_hat = self.
Fr * preqs.Pr_hat / preqs.denom
344 Kn_hat = self.
Fn * preqs.Pn_hat / preqs.denom
345 if debug
and self.config.doTrimKernels:
348 ps = (Kn_hat.shape[1] - 80)//2
349 Kn = _filterKernel(np.fft.ifft2(Kn_hat), ps)
350 Kn_hat = np.fft.fft2(Kn)
351 Kr = _filterKernel(np.fft.ifft2(Kr_hat), ps)
352 Kr_hat = np.fft.fft2(Kr)
354 def processImages(im1, im2, doAdd=False):
356 im1[np.isinf(im1)] = np.nan
357 im1[np.isnan(im1)] = np.nanmean(im1)
358 im2[np.isinf(im2)] = np.nan
359 im2[np.isnan(im2)] = np.nanmean(im2)
361 R_hat = np.fft.fft2(im1)
362 N_hat = np.fft.fft2(im2)
364 D_hat = Kr_hat * N_hat
366 D_hat -= Kn_hat * R_hat
368 D_hat += Kn_hat * R_hat
370 D = np.fft.ifft2(D_hat)
371 D = np.fft.ifftshift(D.real) / preqs.Fd
375 D = processImages(self.
im1, self.
im2, doAdd=
False)
379 return pipeBase.Struct(D=D, D_var=D_var)
382 """! Convolve an Exposure with a decorrelation convolution kernel.
386 exposure : lsst.afw.image.Exposure to be convolved.
387 kernel : 2D numpy.array to convolve the image with
391 A new lsst.afw.image.Exposure with the convolved pixels and the (possibly
396 - We optionally re-center the kernel if necessary and return the possibly
399 kernelImg = afwImage.ImageD(kernel.shape[0], kernel.shape[1])
400 kernelImg.getArray()[:, :] = kernel
401 kern = afwMath.FixedKernel(kernelImg)
403 maxloc = np.unravel_index(np.argmax(kernel), kernel.shape)
404 kern.setCtrX(maxloc[0])
405 kern.setCtrY(maxloc[1])
406 outExp = exposure.clone()
407 convCntrl = afwMath.ConvolutionControl(doNormalize=
False, doCopyEdge=
False,
408 maxInterpolationDistance=0)
410 afwMath.convolve(outExp.getMaskedImage(), exposure.getMaskedImage(), kern, convCntrl)
413 afwMath.convolve(outExp, exposure, kern, convCntrl)
418 """Compute ZOGY diffim `D` using image-space convlutions
420 This method is still being debugged as it results in artifacts
421 when the PSFs are noisy (see module-level docstring). Thus
422 there are several options still enabled by the `debug` flag,
423 which are disabled by defult.
427 padSize : int, the amount to pad the PSFs by
428 debug : bool, flag to enable debugging tests and options
432 D : lsst.afw.Exposure
433 the proper image difference, including correct variance,
441 Kr_hat = (preqs.Pr_hat + delta) / (preqs.denom + delta)
442 Kn_hat = (preqs.Pn_hat + delta) / (preqs.denom + delta)
443 Kr = np.fft.ifft2(Kr_hat).real
444 Kr = np.roll(np.roll(Kr, -1, 0), -1, 1)
445 Kn = np.fft.ifft2(Kn_hat).real
446 Kn = np.roll(np.roll(Kn, -1, 0), -1, 1)
448 def _trimKernel(self, K, trim_amount):
452 K = K[ps:-ps, ps:-ps]
455 padSize = self.
padSize if padSize
is None else padSize
457 if debug
and self.config.doTrimKernels:
460 Kn = _trimKernel(Kn, padSize)
461 Kr = _trimKernel(Kr, padSize)
467 tmp = D.getMaskedImage()
468 tmp -= exp1.getMaskedImage()
473 """Utility method to set an exposure's PSF when provided as a 2-d numpy.array
475 psfI = afwImage.ImageD(psfArr.shape[0], psfArr.shape[1])
476 psfI.getArray()[:, :] = psfArr
477 psfK = afwMath.FixedKernel(psfI)
478 psfNew = measAlg.KernelPsf(psfK)
479 exposure.setPsf(psfNew)
483 """Wrapper method to compute ZOGY proper diffim
485 This method should be used as the public interface for
486 computing the ZOGY diffim.
491 Override config `inImageSpace` parameter
493 Override config `padSize` parameter
495 additional keyword arguments to be passed to
496 `computeDiffimFourierSpace` or `computeDiffimImageSpace`.
500 D : lsst.afw.Exposure
501 the proper image difference, including correct variance,
504 inImageSpace = self.config.inImageSpace
if inImageSpace
is None else inImageSpace
506 padSize = self.
padSize if padSize
is None else padSize
510 D = self.science.clone()
511 D.getMaskedImage().getImage().getArray()[:, :] = res.D
512 D.getMaskedImage().getVariance().getArray()[:, :] = res.D_var
519 """Compute the ZOGY diffim PSF (ZOGY manuscript eq. 14)
524 Override config `padSize` parameter
526 Return the FFT of the diffim PSF (do not inverse-FFT it)
527 psf1 : 2D numpy.array
528 (Optional) Input psf of template, override if already padded
529 psf2 : 2D numpy.array
530 (Optional) Input psf of science image, override if already padded
534 Pd : 2D numpy.array, the diffim PSF (or FFT of PSF if `keepFourier=True`)
536 preqs = self.
computePrereqs(psf1=psf1, psf2=psf2, padSize=padSize)
538 Pd_hat_numerator = (self.
Fr * self.
Fn * preqs.Pr_hat * preqs.Pn_hat)
539 Pd_hat = Pd_hat_numerator / (preqs.Fd * preqs.denom)
544 Pd = np.fft.ifft2(Pd_hat)
545 Pd = np.fft.ifftshift(Pd).real
550 R_hat=
None, Kr_hat=
None, Kr=
None,
551 N_hat=
None, Kn_hat=
None, Kn=
None):
552 """Compute the astrometric noise correction terms
554 Compute the correction for estimated astrometric noise as
555 proscribed in ZOGY (2016), section 3.3. All convolutions
556 performed either in real (image) or Fourier space.
560 xVarAst, yVarAst : float
561 estimated astrometric noise (variance of astrometric registration errors)
563 Perform all convolutions in real (image) space rather than Fourier space
564 R_hat : 2-D numpy.array
565 (Optional) FFT of template image, only required if `inImageSpace=False`
566 Kr_hat : 2-D numpy.array
567 FFT of Kr kernel (eq. 28 of ZOGY (2016)), only required if `inImageSpace=False`
569 Kr kernel (eq. 28 of ZOGY (2016)), only required if `inImageSpace=True`.
570 Kr is associated with the template (reference).
571 N_hat : 2-D numpy.array
572 FFT of science image, only required if `inImageSpace=False`
573 Kn_hat : 2-D numpy.array
574 FFT of Kn kernel (eq. 29 of ZOGY (2016)), only required if `inImageSpace=False`
576 Kn kernel (eq. 29 of ZOGY (2016)), only required if `inImageSpace=True`.
577 Kn is associated with the science (new) image.
581 VastSR, VastSN : 2-D numpy.arrays containing the values in eqs. 30 and 32 of
585 if xVarAst + yVarAst > 0:
588 S_R = S_R.getMaskedImage().getImage().getArray()
590 S_R = np.fft.ifft2(R_hat * Kr_hat)
591 gradRx, gradRy = np.gradient(S_R)
592 VastSR = xVarAst * gradRx**2. + yVarAst * gradRy**2.
596 S_N = S_N.getMaskedImage().getImage().getArray()
598 S_N = np.fft.ifft2(N_hat * Kn_hat)
599 gradNx, gradNy = np.gradient(S_N)
600 VastSN = xVarAst * gradNx**2. + yVarAst * gradNy**2.
602 return VastSR, VastSN
605 """Compute corrected likelihood image, optimal for source detection
607 Compute ZOGY S_corr image. This image can be thresholded for
608 detection without optimal filtering, and the variance image is
609 corrected to account for astrometric noise (errors in
610 astrometric registration whether systematic or due to effects
611 such as DCR). The calculations here are all performed in
612 Fourier space, as proscribed in ZOGY (2016).
616 xVarAst, yVarAst : float
617 estimated astrometric noise (variance of astrometric registration errors)
621 A lsst.pipe.base.Struct containing:
622 - S : numpy.array, the likelihood image S (eq. 12 of ZOGY (2016))
623 - S_var : the corrected variance image (denominator of eq. 25 of ZOGY (2016))
624 - Dpsf : the PSF of the diffim D, likely never to be used.
627 psf1 = ZogyTask._padPsfToImageSize(self.
im1_psf, self.
im1)
628 psf2 = ZogyTask._padPsfToImageSize(self.
im2_psf, self.
im2)
633 R_hat = np.fft.fft2(self.
im1)
634 N_hat = np.fft.fft2(self.
im2)
635 D_hat = self.
Fr * preqs.Pr_hat * N_hat - self.
Fn * preqs.Pn_hat * R_hat
638 Pd_hat = self.
computeDiffimPsf(padSize=0, keepFourier=
True, psf1=psf1, psf2=psf2)
639 Pd_bar = np.conj(Pd_hat)
640 S = np.fft.ifft2(D_hat * Pd_bar)
644 Pn_hat2 = np.conj(preqs.Pn_hat) * preqs.Pn_hat
645 Kr_hat = self.
Fr * self.
Fn**2. * np.conj(preqs.Pr_hat) * Pn_hat2 / preqs.denom**2.
646 Pr_hat2 = np.conj(preqs.Pr_hat) * preqs.Pr_hat
647 Kn_hat = self.
Fn * self.
Fr**2. * np.conj(preqs.Pn_hat) * Pr_hat2 / preqs.denom**2.
649 Kr_hat2 = np.fft.fft2(np.fft.ifft2(Kr_hat)**2.)
650 Kn_hat2 = np.fft.fft2(np.fft.ifft2(Kn_hat)**2.)
651 var1c_hat = Kr_hat2 * np.fft.fft2(self.
im1_var)
652 var2c_hat = Kn_hat2 * np.fft.fft2(self.
im2_var)
656 R_hat=R_hat, Kr_hat=Kr_hat,
657 N_hat=N_hat, Kn_hat=Kn_hat)
659 S_var = np.sqrt(np.fft.ifftshift(np.fft.ifft2(var1c_hat + var2c_hat)) + fGradR + fGradN)
662 S = np.fft.ifftshift(np.fft.ifft2(Kn_hat * N_hat - Kr_hat * R_hat))
666 return pipeBase.Struct(S=S.real, S_var=S_var.real, Dpsf=Pd)
669 """Compute corrected likelihood image, optimal for source detection
671 Compute ZOGY S_corr image. This image can be thresholded for
672 detection without optimal filtering, and the variance image is
673 corrected to account for astrometric noise (errors in
674 astrometric registration whether systematic or due to effects
675 such as DCR). The calculations here are all performed in
680 xVarAst, yVarAst : float
681 estimated astrometric noise (variance of astrometric registration errors)
686 - S : lsst.afw.image.Exposure, the likelihood exposure S (eq. 12 of ZOGY (2016)),
687 including corrected variance, masks, and PSF
688 - D : lsst.afw.Exposure, the proper image difference, including correct
689 variance, masks, and PSF
694 padSize = self.
padSize if padSize
is None else padSize
698 Pd_bar = np.fliplr(np.flipud(Pd))
700 tmp = S.getMaskedImage()
705 Pn_hat2 = np.conj(preqs.Pn_hat) * preqs.Pn_hat
706 Kr_hat = self.
Fr * self.
Fn**2. * np.conj(preqs.Pr_hat) * Pn_hat2 / preqs.denom**2.
707 Pr_hat2 = np.conj(preqs.Pr_hat) * preqs.Pr_hat
708 Kn_hat = self.
Fn * self.
Fr**2. * np.conj(preqs.Pn_hat) * Pr_hat2 / preqs.denom**2.
710 Kr = np.fft.ifft2(Kr_hat).real
711 Kr = np.roll(np.roll(Kr, -1, 0), -1, 1)
712 Kn = np.fft.ifft2(Kn_hat).real
713 Kn = np.roll(np.roll(Kn, -1, 0), -1, 1)
714 var1c, _ = self.
_doConvolve(self.template.getMaskedImage().getVariance(), Kr**2.)
715 var2c, _ = self.
_doConvolve(self.science.getMaskedImage().getVariance(), Kn**2.)
721 Smi = S.getMaskedImage()
723 S_var = np.sqrt(var1c.getArray() + var2c.getArray() + fGradR + fGradN)
724 S.getMaskedImage().getVariance().getArray()[:, :] = S_var
729 def computeScorr(self, xVarAst=0., yVarAst=0., inImageSpace=None, padSize=0, **kwargs):
730 """Wrapper method to compute ZOGY corrected likelihood image, optimal for
733 This method should be used as the public interface for
734 computing the ZOGY S_corr.
738 xVarAst, yVarAst : float
739 estimated astrometric noise (variance of astrometric registration errors)
741 Override config `inImageSpace` parameter
743 Override config `padSize` parameter
747 S : lsst.afw.image.Exposure, the likelihood exposure S (eq. 12 of ZOGY (2016)),
748 including corrected variance, masks, and PSF
750 inImageSpace = self.config.inImageSpace
if inImageSpace
is None else inImageSpace
756 S = self.science.clone()
757 S.getMaskedImage().getImage().getArray()[:, :] = res.S
758 S.getMaskedImage().getVariance().getArray()[:, :] = res.S_var
765 """Task to be used as an ImageMapperSubtask for performing
766 ZOGY image subtraction on a grid of subimages.
768 ConfigClass = ZogyConfig
769 _DefaultName =
'ip_diffim_ZogyMapper'
772 ImageMapperSubtask.__init__(self, *args, **kwargs)
774 def run(self, subExposure, expandedSubExposure, fullBBox, template,
776 """Perform ZOGY proper image subtraction on sub-images
778 This method performs ZOGY proper image subtraction on
779 `subExposure` using local measures for image variances and
780 PSF. `subExposure` is a sub-exposure of the science image. It
781 also requires the corresponding sub-exposures of the template
782 (`template`). The operations are actually performed on
783 `expandedSubExposure` to allow for invalid edge pixels arising
784 from convolutions, which are then removed.
788 subExposure : lsst.afw.image.Exposure
789 the sub-exposure of the diffim
790 expandedSubExposure : lsst.afw.image.Exposure
791 the expanded sub-exposure upon which to operate
792 fullBBox : lsst.afw.geom.BoundingBox
793 the bounding box of the original exposure
794 template : lsst.afw.image.Exposure
795 the template exposure, from which a corresponding sub-exposure
798 additional keyword arguments propagated from
799 `ImageMapReduceTask.run`. These include:
801 Compute and return the corrected likelihood image S_corr
802 rather than the proper image difference
803 - inImageSpace : bool
804 Perform all convolutions in real (image) space rather than
805 in Fourier space. This option currently leads to artifacts
806 when using real (measured and noisy) PSFs, thus it is set
807 to `False` by default.
808 These kwargs may also include arguments to be propagated to
809 `ZogyTask.computeDiffim` and `ZogyTask.computeScorr`.
813 A `lsst.pipe.base.Struct` containing the result of the
814 `subExposure` processing, labelled 'subExposure'. In this case
815 it is either the subExposure of the proper image difference D,
816 or (if `doScorr==True`) the corrected likelihood exposure `S`.
820 This `run` method accepts parameters identical to those of
821 `ImageMapperSubtask.run`, since it is called from the
822 `ImageMapperTask`. See that class for more information.
824 bbox = subExposure.getBBox()
825 center = ((bbox.getBeginX() + bbox.getEndX()) // 2., (bbox.getBeginY() + bbox.getEndY()) // 2.)
826 center = afwGeom.Point2D(center[0], center[1])
828 imageSpace = kwargs.pop(
'inImageSpace',
False)
829 doScorr = kwargs.pop(
'doScorr',
False)
830 sigmas = kwargs.pop(
'sigmas',
None)
831 padSize = kwargs.pop(
'padSize', 7)
834 subExp2 = expandedSubExposure
837 subExp1 = template.Factory(template, expandedSubExposure.getBBox())
843 sig1, sig2 = sigmas[0], sigmas[1]
845 def _makePsfSquare(psf):
846 if psf.shape[0] < psf.shape[1]:
848 psf = np.pad(psf, ((1, 1), (0, 0)), mode=
'constant')
849 elif psf.shape[0] > psf.shape[1]:
850 psf = np.pad(psf, ((0, 0), (1, 1)), mode=
'constant')
853 psf2 = subExp2.getPsf().computeKernelImage(center).getArray()
854 psf2 = _makePsfSquare(psf2)
856 psf1 = template.getPsf().computeKernelImage(center).getArray()
857 psf1 = _makePsfSquare(psf1)
860 if subExp1.getDimensions()[0] < psf1.shape[0]
or subExp1.getDimensions()[1] < psf1.shape[1]:
861 return pipeBase.Struct(subExposure=subExposure)
864 """Filter a noisy Psf to remove artifacts. Subject of future research."""
866 if psf.shape[0] == 41:
869 psf[0:10, :] = psf[:, 0:10] = psf[31:41, :] = psf[:, 31:41] = 0
875 if self.config.doFilterPsfs:
877 psf1b = _filterPsf(psf1)
878 psf2b = _filterPsf(psf2)
881 if imageSpace
is True:
882 config.inImageSpace = imageSpace
883 config.padSize = padSize
884 task =
ZogyTask(templateExposure=subExp1, scienceExposure=subExp2,
885 sig1=sig1, sig2=sig2, psf1=psf1b, psf2=psf2b, config=config)
888 D = task.computeDiffim(**kwargs)
890 D = task.computeScorr(**kwargs)
892 outExp = D.Factory(D, subExposure.getBBox())
893 out = pipeBase.Struct(subExposure=outExp)
898 mapperSubtask = pexConfig.ConfigurableField(
899 doc=
'Zogy subtask to run on each sub-image',
900 target=ZogyMapperSubtask
def _doConvolve
Convolve an Exposure with a decorrelation convolution kernel.
def computeDiffimImageSpace
def _computeVarAstGradients
def computeDiffimFourierSpace
def computeScorrImageSpace
def computeScorrFourierSpace