LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
Public Member Functions | Static Public Member Functions | Public Attributes | Static Public Attributes | List of all members
lsst.ip.diffim.zogy.ZogyTask Class Reference
Inheritance diagram for lsst.ip.diffim.zogy.ZogyTask:

Public Member Functions

def initializeSubImage (self, fullExp, innerBox, outerBox, noiseMeanVar, useNoise=True)
 
def makeSpatialPsf (self, gridPsfs)
 
def padAndFftImage (self, imgArr)
 
def removeNonFinitePixels (self, imgArr)
 
def inverseFftAndCropImage (self, imgArr, origSize, filtInf=None, filtNaN=None, dtype=None)
 
def prepareFullExposure (self, exposure1, exposure2, correctBackground=False)
 
def prepareSubExposure (self, localCutout, psf1=None, psf2=None, sig1=None, sig2=None)
 
def checkCentroids (self, psfArr1, psfArr2)
 
def calculateFourierDiffim (self, psf1, im1, varPlane1, F1, varMean1, psf2, im2, varPlane2, F2, varMean2, calculateScore=True)
 
def pasteSubDiffImg (self, ftDiff, diffExp, scoreExp=None)
 
def finishResultExposures (self, diffExp, scoreExp=None)
 
def run (self, exposure1, exposure2, calculateScore=True)
 

Static Public Member Functions

def padCenterOriginArray (A, newShape, useInverse=False, dtype=None)
 
def estimateMatchingKernelSize (psf1, psf2)
 
def splitBorder (innerBox, outerBox)
 
def generateGrid (imageBox, minEdgeDims, innerBoxDims, minTotalDims=None, powerOfTwo=False)
 
def computePsfAtCenter (exposure)
 
def subtractImageMean (image, mask, statsControl)
 
def pixelSpaceSquare (D)
 
def getCentroid (A)
 
def calculateMaskPlane (mask1, mask2, effPsf1=None, effPsf2=None)
 
def makeKernelPsfFromArray (A)
 

Public Attributes

 statsControl
 
 fullExpVar1
 
 fullExpVar2
 
 F1
 
 F2
 
 borderSize
 
 filtsImg1
 
 filtsImg2
 
 filtsVar1
 
 filtsVar2
 
 fullExp1
 
 fullExp2
 
 cutBoxes1
 
 cutBoxes2
 
 subExpPsf1
 
 subExpPsf2
 
 psfShape1
 
 psfShape2
 
 subExpVar1
 
 subExpVar2
 
 freqSpaceShape
 
 subImg1
 
 subImg2
 
 subImgFft1
 
 subVarImgFft1
 
 subImgFft2
 
 subVarImgFft2
 
 psfFft1
 
 psfFft2
 
 gridPsfs
 

Static Public Attributes

 ConfigClass = ZogyConfig
 

Detailed Description

Task to perform ZOGY proper image subtraction. See module-level documentation for
additional details.

Definition at line 124 of file zogy.py.

Member Function Documentation

◆ calculateFourierDiffim()

def lsst.ip.diffim.zogy.ZogyTask.calculateFourierDiffim (   self,
  psf1,
  im1,
  varPlane1,
  F1,
  varMean1,
  psf2,
  im2,
  varPlane2,
  F2,
  varMean2,
  calculateScore = True 
)
Convolve and subtract two images in Fourier space.

Calculate the ZOGY proper difference image, score image and their PSFs.
All input and output arrays are in Fourier space.

Parameters
----------
psf1, psf2 : `numpy.ndarray`, (``self.freqSpaceShape``,)
    Psf arrays. Must be already in Fourier space.
im1, im2 : `numpy.ndarray`, (``self.freqSpaceShape``,)
    Image arrays. Must be already in Fourier space.
varPlane1, varPlane2 : `numpy.ndarray`, (``self.freqSpaceShape``,)
    Variance plane arrays respectively. Must be already in Fourier space.
varMean1, varMean2 : `numpy.float` > 0.
    Average per-pixel noise variance in im1, im2 respectively. Used as weighing
    of input images. Must be greater than zero.
F1, F2 : `numpy.float` > 0.
    Photometric scaling of the images. See eqs. (5)--(9)
calculateScore : `bool`, optional
    If True (default), calculate and return the detection significance (score) image.
    Otherwise, these return fields are `None`.

Returns
-------
result : `pipe.base.Struct`
    All arrays are in Fourier space and have shape ``self.freqSpaceShape``.

    ``Fd``
        Photometric level of ``D`` (`float`).
    ``D``
        The difference image (`numpy.ndarray` [`numpy.complex`]).
    ``varplaneD``
        Variance plane of ``D`` (`numpy.ndarray` [`numpy.complex`]).
    ``Pd``
        PSF of ``D`` (`numpy.ndarray` [`numpy.complex`]).
    ``S``
        Significance (score) image (`numpy.ndarray` [`numpy.complex`] or `None`).
    ``varplaneS``
        Variance plane of ``S`` ((`numpy.ndarray` [`numpy.complex`] or `None`).
    ``Ps``
        PSF of ``S`` (`numpy.ndarray` [`numpy.complex`]).

Notes
-----
All array inputs and outputs are Fourier-space images with shape of
`self.freqSpaceShape` in this method.

``varMean1``, ``varMean2`` quantities are part of the noise model and not to be confused
with the variance of image frequency components or with ``varPlane1``, ``varPlane2`` that
are the Fourier transform of the variance planes.

Definition at line 857 of file zogy.py.

858  psf2, im2, varPlane2, F2, varMean2, calculateScore=True):
859  """Convolve and subtract two images in Fourier space.
860 
861  Calculate the ZOGY proper difference image, score image and their PSFs.
862  All input and output arrays are in Fourier space.
863 
864  Parameters
865  ----------
866  psf1, psf2 : `numpy.ndarray`, (``self.freqSpaceShape``,)
867  Psf arrays. Must be already in Fourier space.
868  im1, im2 : `numpy.ndarray`, (``self.freqSpaceShape``,)
869  Image arrays. Must be already in Fourier space.
870  varPlane1, varPlane2 : `numpy.ndarray`, (``self.freqSpaceShape``,)
871  Variance plane arrays respectively. Must be already in Fourier space.
872  varMean1, varMean2 : `numpy.float` > 0.
873  Average per-pixel noise variance in im1, im2 respectively. Used as weighing
874  of input images. Must be greater than zero.
875  F1, F2 : `numpy.float` > 0.
876  Photometric scaling of the images. See eqs. (5)--(9)
877  calculateScore : `bool`, optional
878  If True (default), calculate and return the detection significance (score) image.
879  Otherwise, these return fields are `None`.
880 
881  Returns
882  -------
883  result : `pipe.base.Struct`
884  All arrays are in Fourier space and have shape ``self.freqSpaceShape``.
885 
886  ``Fd``
887  Photometric level of ``D`` (`float`).
888  ``D``
889  The difference image (`numpy.ndarray` [`numpy.complex`]).
890  ``varplaneD``
891  Variance plane of ``D`` (`numpy.ndarray` [`numpy.complex`]).
892  ``Pd``
893  PSF of ``D`` (`numpy.ndarray` [`numpy.complex`]).
894  ``S``
895  Significance (score) image (`numpy.ndarray` [`numpy.complex`] or `None`).
896  ``varplaneS``
897  Variance plane of ``S`` ((`numpy.ndarray` [`numpy.complex`] or `None`).
898  ``Ps``
899  PSF of ``S`` (`numpy.ndarray` [`numpy.complex`]).
900 
901  Notes
902  -----
903  All array inputs and outputs are Fourier-space images with shape of
904  `self.freqSpaceShape` in this method.
905 
906  ``varMean1``, ``varMean2`` quantities are part of the noise model and not to be confused
907  with the variance of image frequency components or with ``varPlane1``, ``varPlane2`` that
908  are the Fourier transform of the variance planes.
909  """
910  var1F2Sq = varMean1*F2*F2
911  var2F1Sq = varMean2*F1*F1
912  # We need reals for comparison, also real operations are usually faster
913  psfAbsSq1 = np.real(np.conj(psf1)*psf1)
914  psfAbsSq2 = np.real(np.conj(psf2)*psf2)
915  FdDenom = np.sqrt(var1F2Sq + var2F1Sq) # one number
916 
917  # Secure positive limit to avoid floating point operations resulting in exact zero
918  tiny = np.finfo(psf1.dtype).tiny * 100
919  sDenom = var1F2Sq*psfAbsSq2 + var2F1Sq*psfAbsSq1 # array, eq. (12)
920  # Frequencies where both psfs are too close to zero.
921  # We expect this only in cases when psf1, psf2 are identical,
922  # and either having very well sampled Gaussian tails
923  # or having "edges" such that some sinc-like zero crossings are found at symmetry points
924  #
925  # if sDenom < tiny then it can be == 0. -> `denom` = 0. and 0/0 occur at `c1` , `c2`
926  # if we keep SDenom = tiny, denom ~ O(sqrt(tiny)), Pd ~ O(sqrt(tiny)), S ~ O(sqrt(tiny)*tiny) == 0
927  # Where S = 0 then Pd = 0 and D should still yield the same variance ~ O(1)
928  # For safety, we set S = 0 explicitly, too, though it should be unnecessary.
929  fltZero = sDenom < tiny
930  nZero = np.sum(fltZero)
931  self.log.debug("There are %s frequencies where both FFTd PSFs are close to zero.", nZero)
932  if nZero > 0:
933  # We expect only a small fraction of such frequencies
934  fltZero = np.nonzero(fltZero) # Tuple of index arrays
935  sDenom[fltZero] = tiny # Avoid division problem but overwrite result anyway
936  denom = np.sqrt(sDenom) # array, eq. (13)
937 
938  c1 = F2*psf2/denom
939  c2 = F1*psf1/denom
940  if nZero > 0:
941  c1[fltZero] = F2/FdDenom
942  c2[fltZero] = F1/FdDenom
943  D = c1*im1 - c2*im2 # Difference image eq. (13)
944  varPlaneD = self.pixelSpaceSquare(c1)*varPlane1 + self.pixelSpaceSquare(c2)*varPlane2 # eq. (26)
945 
946  Pd = FdDenom*psf1*psf2/denom # Psf of D eq. (14)
947  if nZero > 0:
948  Pd[fltZero] = 0
949 
950  Fd = F1*F2/FdDenom # Flux scaling of D eq. (15)
951  if calculateScore:
952  c1 = F1*F2*F2*np.conj(psf1)*psfAbsSq2/sDenom
953  c2 = F2*F1*F1*np.conj(psf2)*psfAbsSq1/sDenom
954  if nZero > 0:
955  c1[fltZero] = 0
956  c2[fltZero] = 0
957  S = c1*im1 - c2*im2 # eq. (12)
958  varPlaneS = self.pixelSpaceSquare(c1)*varPlane1 + self.pixelSpaceSquare(c2)*varPlane2
959  Ps = np.conj(Pd)*Pd # eq. (17) Source detection expects a PSF
960  else:
961  S = None
962  Ps = None
963  varPlaneS = None
964  return pipeBase.Struct(D=D, Pd=Pd, varPlaneD=varPlaneD, Fd=Fd,
965  S=S, Ps=Ps, varPlaneS=varPlaneS)
966 

◆ calculateMaskPlane()

def lsst.ip.diffim.zogy.ZogyTask.calculateMaskPlane (   mask1,
  mask2,
  effPsf1 = None,
  effPsf2 = None 
)
static
Calculate the mask plane of the difference image.

Parameters
----------
mask1, maks2 : `lsst.afw.image.Mask`
    Mask planes of the two exposures.


Returns
-------
diffmask : `lsst.afw.image.Mask`
    Mask plane for the subtraction result.

Notes
-----
TODO DM-25174 : Specification of effPsf1, effPsf2 are not yet supported.

Definition at line 968 of file zogy.py.

968  def calculateMaskPlane(mask1, mask2, effPsf1=None, effPsf2=None):
969  """Calculate the mask plane of the difference image.
970 
971  Parameters
972  ----------
973  mask1, maks2 : `lsst.afw.image.Mask`
974  Mask planes of the two exposures.
975 
976 
977  Returns
978  -------
979  diffmask : `lsst.afw.image.Mask`
980  Mask plane for the subtraction result.
981 
982  Notes
983  -----
984  TODO DM-25174 : Specification of effPsf1, effPsf2 are not yet supported.
985  """
986 
987  # mask1 x effPsf2 | mask2 x effPsf1
988  if effPsf1 is not None or effPsf2 is not None:
989  # TODO: DM-25174 effPsf1, effPsf2: the effective psf for cross-blurring.
990  # We need a "size" approximation of the c1 and c2 coefficients to make effPsfs
991  # Also convolution not yet supports mask-only operation
992  raise NotImplementedError("Mask plane only 'convolution' operation is not yet supported")
993  R = mask1.clone()
994  R |= mask2
995  return R
996 

◆ checkCentroids()

def lsst.ip.diffim.zogy.ZogyTask.checkCentroids (   self,
  psfArr1,
  psfArr2 
)
Check whether two PSF array centroids' distance is within tolerance.

Parameters
----------
psfArr1, psfArr2 : `numpy.ndarray` of `float`
    Input PSF arrays to check.

Returns
-------
None

Raises
------
ValueError:
    Centroid distance exceeds `config.maxPsfCentroidDist` pixels.

Definition at line 832 of file zogy.py.

832  def checkCentroids(self, psfArr1, psfArr2):
833  """Check whether two PSF array centroids' distance is within tolerance.
834 
835  Parameters
836  ----------
837  psfArr1, psfArr2 : `numpy.ndarray` of `float`
838  Input PSF arrays to check.
839 
840  Returns
841  -------
842  None
843 
844  Raises
845  ------
846  ValueError:
847  Centroid distance exceeds `config.maxPsfCentroidDist` pixels.
848  """
849  yc1, xc1 = self.getCentroid(psfArr1)
850  yc2, xc2 = self.getCentroid(psfArr2)
851  dy = yc2 - yc1
852  dx = xc2 - xc1
853  if dy*dy + dx*dx > self.config.maxPsfCentroidDist*self.config.maxPsfCentroidDist:
854  raise ValueError(
855  f"PSF centroids are offset by more than {self.config.maxPsfCentroidDist:.2f} pixels.")
856 

◆ computePsfAtCenter()

def lsst.ip.diffim.zogy.ZogyTask.computePsfAtCenter (   exposure)
static
Computes the PSF image at the bbox center point.

This may be at a fractional pixel position.

Parameters
----------
exposure : `lsst.afw.image.Exposure`
    Exposure with psf.

Returns
-------
psfImg : `lsst.afw.image.Image`
    Calculated psf image.

Definition at line 562 of file zogy.py.

562  def computePsfAtCenter(exposure):
563  """Computes the PSF image at the bbox center point.
564 
565  This may be at a fractional pixel position.
566 
567  Parameters
568  ----------
569  exposure : `lsst.afw.image.Exposure`
570  Exposure with psf.
571 
572  Returns
573  -------
574  psfImg : `lsst.afw.image.Image`
575  Calculated psf image.
576  """
577  pBox = exposure.getBBox()
578  cen = pBox.getCenter()
579  psf = exposure.getPsf()
580  psfImg = psf.computeKernelImage(cen) # Centered and normed
581  return psfImg
582 

◆ estimateMatchingKernelSize()

def lsst.ip.diffim.zogy.ZogyTask.estimateMatchingKernelSize (   psf1,
  psf2 
)
static
Estimate the image space size of the matching kernels.

Return ten times the larger Gaussian sigma estimate but at least
the largest of the original psf dimensions.

Parameters
----------
psf1, psf2 : `lsst.afw.detection.Psf`
    The PSFs of the two input exposures.

Returns
-------
size : `int`
    Conservative estimate for matching kernel size in pixels.
    This is the minimum padding around the inner region at each side.

Notes
-----

Definition at line 269 of file zogy.py.

269  def estimateMatchingKernelSize(psf1, psf2):
270  """Estimate the image space size of the matching kernels.
271 
272  Return ten times the larger Gaussian sigma estimate but at least
273  the largest of the original psf dimensions.
274 
275  Parameters
276  ----------
277  psf1, psf2 : `lsst.afw.detection.Psf`
278  The PSFs of the two input exposures.
279 
280  Returns
281  -------
282  size : `int`
283  Conservative estimate for matching kernel size in pixels.
284  This is the minimum padding around the inner region at each side.
285 
286  Notes
287  -----
288  """
289  sig1 = psf1.computeShape().getDeterminantRadius()
290  sig2 = psf2.computeShape().getDeterminantRadius()
291  sig = max(sig1, sig2)
292  psfBBox1 = psf1.computeBBox()
293  psfBBox2 = psf2.computeBBox()
294  return max(10 * sig, psfBBox1.getWidth(), psfBBox1.getHeight(),
295  psfBBox2.getWidth(), psfBBox2.getHeight())
296 
int max

◆ finishResultExposures()

def lsst.ip.diffim.zogy.ZogyTask.finishResultExposures (   self,
  diffExp,
  scoreExp = None 
)
Perform final steps on the full difference exposure result.

Set photometric calibration, psf properties of the exposures.

Parameters
----------
diffExp : `lsst.afw.image.Exposure`
    The result difference image exposure to finalize.
scoreExp : `lsst.afw.image.Exposure` or `None`
    The result score exposure to finalize.

Returns
-------
None.

Definition at line 1105 of file zogy.py.

1105  def finishResultExposures(self, diffExp, scoreExp=None):
1106  """Perform final steps on the full difference exposure result.
1107 
1108  Set photometric calibration, psf properties of the exposures.
1109 
1110  Parameters
1111  ----------
1112  diffExp : `lsst.afw.image.Exposure`
1113  The result difference image exposure to finalize.
1114  scoreExp : `lsst.afw.image.Exposure` or `None`
1115  The result score exposure to finalize.
1116 
1117  Returns
1118  -------
1119  None.
1120  """
1121  # Set Calibration and PSF of the result exposures
1122  calibOne = afwImage.PhotoCalib(1.)
1123  diffExp.setPhotoCalib(calibOne)
1124  # Create the spatially varying PSF and set it for the diffExp
1125  # Set the PSF of this subExposure
1126  if len(self.gridPsfs) > 1:
1127  diffExp.setPsf(
1128  self.makeSpatialPsf(
1129  pipeBase.Struct(bbox=x.bbox, psf=x.Pd) for x in self.gridPsfs
1130  ))
1131  if scoreExp is not None:
1132  scoreExp.setPsf(
1133  self.makeSpatialPsf(
1134  pipeBase.Struct(bbox=x.bbox, psf=x.Ps) for x in self.gridPsfs
1135  ))
1136  else:
1137  # We did not have a grid, use the result psf without
1138  # making a CoaddPsf
1139  diffExp.setPsf(self.gridPsfs[0].Pd)
1140  if scoreExp is not None:
1141  scoreExp.setPsf(self.gridPsfs[0].Ps)
1142 
1143  # diffExp.setPsf(self.makeKernelPsfFromArray(Pd))
1144  if scoreExp is not None:
1145  scoreExp.setPhotoCalib(calibOne)
1146  # Zero score exposure where its variance is zero or the inputs are non-finite
1147  flt = (self.filtsImg1.filtInf | self.filtsImg2.filtInf
1148  | self.filtsImg1.filtNaN | self.filtsImg2.filtNaN
1149  | self.filtsVar1.filtInf | self.filtsVar2.filtInf
1150  | self.filtsVar1.filtNaN | self.filtsVar2.filtNaN)
1151  # Ensure that no division by 0 occurs in S/sigma(S).
1152  # S is set to be always finite, 0 where pixels non-finite
1153  tiny = np.finfo(scoreExp.variance.dtype).tiny * 100
1154  flt = np.logical_or(flt, scoreExp.variance.array < tiny)
1155  # Don't set variance to tiny.
1156  # It becomes 0 in case of conversion to single precision.
1157  # Set variance to 1, indicating that zero is in units of "sigmas" already.
1158  scoreExp.variance.array[flt] = 1
1159  scoreExp.image.array[flt] = 0
1160 
The photometric calibration of an exposure.
Definition: PhotoCalib.h:114

◆ generateGrid()

def lsst.ip.diffim.zogy.ZogyTask.generateGrid (   imageBox,
  minEdgeDims,
  innerBoxDims,
  minTotalDims = None,
  powerOfTwo = False 
)
static
Generate a splitting grid for an image.

The inner boxes cover the input image without overlap, the edges around the inner boxes do overlap
and go beyond the image at the image edges.

Parameters
----------
imageBox : `lsst.geom.Box2I`
    Bounding box of the exposure to split.
minEdgeDims : `lsst.geom.Extent2I`
    Minimum edge width in (x,y) directions each side.
innerBoxDims : `lsst.geom.Extent2I`
    Minimum requested inner box dimensions (x,y).
    The actual dimensions can be larger due to rounding.
minTotalDims: `lsst.geom.Extent2I`, optional
    If provided, minimum total outer dimensions (x,y). The edge will be increased until satisfied.
powerOfTwo : `bool`, optional
    If True, the outer box dimensions should be rounded up to a power of 2
    by increasing the border size. This is up to 8192, above this size,
    rounding up is disabled.

Notes
-----
Inner box dimensions are chosen to be as uniform as they can, remainder pixels at the edge of the
input will be appended to the last column/row boxes.

See diffimTests/tickets/DM-28928_spatial_grid notebooks for demonstration of this code.

This method can be used for both PARENT and LOCAL bounding boxes.

The outerBox dimensions are always even.

Returns
-------
boxList : `list` of `lsst.pipe.base.Struct`
  ``innerBox``, ``outerBox`` : `lsst.geom.Box2I`, inner boxes and overlapping border around them.

Definition at line 341 of file zogy.py.

341  def generateGrid(imageBox, minEdgeDims, innerBoxDims, minTotalDims=None, powerOfTwo=False):
342  """Generate a splitting grid for an image.
343 
344  The inner boxes cover the input image without overlap, the edges around the inner boxes do overlap
345  and go beyond the image at the image edges.
346 
347  Parameters
348  ----------
349  imageBox : `lsst.geom.Box2I`
350  Bounding box of the exposure to split.
351  minEdgeDims : `lsst.geom.Extent2I`
352  Minimum edge width in (x,y) directions each side.
353  innerBoxDims : `lsst.geom.Extent2I`
354  Minimum requested inner box dimensions (x,y).
355  The actual dimensions can be larger due to rounding.
356  minTotalDims: `lsst.geom.Extent2I`, optional
357  If provided, minimum total outer dimensions (x,y). The edge will be increased until satisfied.
358  powerOfTwo : `bool`, optional
359  If True, the outer box dimensions should be rounded up to a power of 2
360  by increasing the border size. This is up to 8192, above this size,
361  rounding up is disabled.
362 
363  Notes
364  -----
365  Inner box dimensions are chosen to be as uniform as they can, remainder pixels at the edge of the
366  input will be appended to the last column/row boxes.
367 
368  See diffimTests/tickets/DM-28928_spatial_grid notebooks for demonstration of this code.
369 
370  This method can be used for both PARENT and LOCAL bounding boxes.
371 
372  The outerBox dimensions are always even.
373 
374  Returns
375  -------
376  boxList : `list` of `lsst.pipe.base.Struct`
377  ``innerBox``, ``outerBox`` : `lsst.geom.Box2I`, inner boxes and overlapping border around them.
378 
379  """
380  powersOf2 = np.array([16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192])
381  doubleEdgeDims = minEdgeDims * 2
382  width, height = imageBox.getDimensions()
383  nX = width // innerBoxDims.x # Round down
384  if nX > 0:
385  innerWidth = width // nX # Round down
386  else:
387  innerWidth = width
388  nX = 1
389  xCorners = np.zeros(nX + 1)
390  xCorners[:-1] = np.arange(nX)*innerWidth + imageBox.minX
391  xCorners[-1] = imageBox.endX
392 
393  nY = height // innerBoxDims.y # Round down
394  if nY > 0:
395  innerHeight = height // nY # Round down
396  else:
397  innerHeight = height
398  nY = 1
399  yCorners = np.zeros(nY + 1)
400  yCorners[:-1] = np.arange(nY)*innerHeight + imageBox.minY
401  yCorners[-1] = imageBox.endY
402 
403  boxes = []
404 
405  for i_y in range(nY):
406  for i_x in range(nX):
407  innerBox = Box2I(Point2I(xCorners[i_x], yCorners[i_y]),
408  Point2I(xCorners[i_x + 1] - 1, yCorners[i_y + 1] - 1))
409 
410  paddedWidth = innerBox.width + doubleEdgeDims.x
411  if minTotalDims is not None and paddedWidth < minTotalDims.width:
412  paddedWidth = minTotalDims.width
413  if powerOfTwo:
414  i2x = np.searchsorted(powersOf2, paddedWidth, side='left')
415  if i2x < len(powersOf2):
416  paddedWidth = powersOf2[i2x]
417  if paddedWidth % 2 == 1:
418  paddedWidth += 1 # Ensure total width is even
419 
420  totalXedge = paddedWidth - innerBox.width
421 
422  paddedHeight = innerBox.height + doubleEdgeDims.y
423  if minTotalDims is not None and paddedHeight < minTotalDims.height:
424  paddedHeight = minTotalDims.height
425  if powerOfTwo:
426  i2y = np.searchsorted(powersOf2, paddedHeight, side='left')
427  if i2y < len(powersOf2):
428  paddedHeight = powersOf2[i2y]
429  if paddedHeight % 2 == 1:
430  paddedHeight += 1 # Ensure total height is even
431  totalYedge = paddedHeight - innerBox.height
432  outerBox = Box2I(Point2I(innerBox.minX - totalXedge//2, innerBox.minY - totalYedge//2),
433  Extent2I(paddedWidth, paddedHeight))
434  boxes.append(pipeBase.Struct(innerBox=innerBox, outerBox=outerBox))
435  return boxes
436 
Extent< int, 2 > Extent2I
Definition: Extent.h:397
Point< int, 2 > Point2I
Definition: Point.h:321

◆ getCentroid()

def lsst.ip.diffim.zogy.ZogyTask.getCentroid (   A)
static
Calculate the centroid coordinates of a 2D array.

Parameters
----------
A : 2D `numpy.ndarray` of `float`
    The input array. Must not be all exact zero.

Notes
-----
Calculates the centroid as if the array represented a 2D geometrical shape with
weights per cell, allowing for "negative" weights. If sum equals to exact (float) zero,
calculates centroid of absolute value array.

The geometrical center is defined as (0,0), independently of the array shape.
For an odd dimension, this is the center of the center pixel,
for an even dimension, this is between the two center pixels.

Returns
-------
ycen, xcen : `tuple` of `float`

Definition at line 798 of file zogy.py.

798  def getCentroid(A):
799  """Calculate the centroid coordinates of a 2D array.
800 
801  Parameters
802  ----------
803  A : 2D `numpy.ndarray` of `float`
804  The input array. Must not be all exact zero.
805 
806  Notes
807  -----
808  Calculates the centroid as if the array represented a 2D geometrical shape with
809  weights per cell, allowing for "negative" weights. If sum equals to exact (float) zero,
810  calculates centroid of absolute value array.
811 
812  The geometrical center is defined as (0,0), independently of the array shape.
813  For an odd dimension, this is the center of the center pixel,
814  for an even dimension, this is between the two center pixels.
815 
816  Returns
817  -------
818  ycen, xcen : `tuple` of `float`
819 
820  """
821  s = np.sum(A)
822  if s == 0.:
823  A = np.fabs(A)
824  s = np.sum(A)
825  w = np.arange(A.shape[0], dtype=float) - (A.shape[0] - 1.)/2
826  ycen = np.sum(w[:, np.newaxis]*A)/s
827  w = np.arange(A.shape[1], dtype=float) - (A.shape[1] - 1.)/2
828  xcen = np.sum(w[np.newaxis, :]*A)/s
829 
830  return ycen, xcen
831 

◆ initializeSubImage()

def lsst.ip.diffim.zogy.ZogyTask.initializeSubImage (   self,
  fullExp,
  innerBox,
  outerBox,
  noiseMeanVar,
  useNoise = True 
)
Initializes a sub image.

Parameters
----------
fullExp : `lsst.afw.image.Exposure`
    The full exposure to cut sub image from.
innerBox : `lsst.geom.Box2I`
    The useful area of the calculation up to the whole bounding box of
    ``fullExp``. ``fullExp`` must contain this box.
outerBox : `lsst.geom.Box2I`
    The overall cutting area. ``outerBox`` must be at least 1 pixel larger
    than ``inneBox`` in all directions and may not be fully contained by
    ``fullExp``.
noiseMeanVar : `float` > 0.
    The noise variance level to initialize variance plane and to generate
    white noise for the non-overlapping region.
useNoise : `bool`, optional
    If True, generate white noise for non-overlapping region. Otherwise,
    zero padding will be used in the non-overlapping region.

Returns
-------
result : `lsst.pipe.base.Struct`
  - ``subImg``, ``subVarImg`` : `lsst.afw.image.ImageD`
    The new sub image and its sub variance plane.

Notes
-----
``innerBox``, ``outerBox`` must be in the PARENT system of ``fullExp``.

Supports the non-grid option when ``innerBox`` equals to the
bounding box of ``fullExp``.

Definition at line 210 of file zogy.py.

210  def initializeSubImage(self, fullExp, innerBox, outerBox, noiseMeanVar, useNoise=True):
211  """Initializes a sub image.
212 
213  Parameters
214  ----------
215  fullExp : `lsst.afw.image.Exposure`
216  The full exposure to cut sub image from.
217  innerBox : `lsst.geom.Box2I`
218  The useful area of the calculation up to the whole bounding box of
219  ``fullExp``. ``fullExp`` must contain this box.
220  outerBox : `lsst.geom.Box2I`
221  The overall cutting area. ``outerBox`` must be at least 1 pixel larger
222  than ``inneBox`` in all directions and may not be fully contained by
223  ``fullExp``.
224  noiseMeanVar : `float` > 0.
225  The noise variance level to initialize variance plane and to generate
226  white noise for the non-overlapping region.
227  useNoise : `bool`, optional
228  If True, generate white noise for non-overlapping region. Otherwise,
229  zero padding will be used in the non-overlapping region.
230 
231  Returns
232  -------
233  result : `lsst.pipe.base.Struct`
234  - ``subImg``, ``subVarImg`` : `lsst.afw.image.ImageD`
235  The new sub image and its sub variance plane.
236 
237  Notes
238  -----
239  ``innerBox``, ``outerBox`` must be in the PARENT system of ``fullExp``.
240 
241  Supports the non-grid option when ``innerBox`` equals to the
242  bounding box of ``fullExp``.
243  """
244  fullBox = fullExp.getBBox()
245  subImg = afwImage.ImageD(outerBox, 0)
246  subVarImg = afwImage.ImageD(outerBox, noiseMeanVar)
247  borderBoxes = self.splitBorder(innerBox, outerBox)
248  # Initialize the border region that are not fully within the exposure
249  if useNoise:
250  rng = np.random.Generator(
251  np.random.PCG64(seed=np.array([noiseMeanVar]).view(int)))
252  noiseSig = np.sqrt(noiseMeanVar)
253  for box in borderBoxes:
254  if not fullBox.contains(box):
255  R = subImg[box].array
256  R[...] = rng.normal(scale=noiseSig, size=R.shape)
257  # Copy data to the fully contained inner region, allowing type conversion
258  subImg[innerBox].array[...] = fullExp.image[innerBox].array
259  subVarImg[innerBox].array[...] = fullExp.variance[innerBox].array
260  # Copy data to border regions that have at least a partial overlap
261  for box in borderBoxes:
262  overlapBox = box.clippedTo(fullBox)
263  if not overlapBox.isEmpty():
264  subImg[overlapBox].array[...] = fullExp.image[overlapBox].array
265  subVarImg[overlapBox].array[...] = fullExp.variance[overlapBox].array
266  return pipeBase.Struct(image=subImg, variance=subVarImg)
267 

◆ inverseFftAndCropImage()

def lsst.ip.diffim.zogy.ZogyTask.inverseFftAndCropImage (   self,
  imgArr,
  origSize,
  filtInf = None,
  filtNaN = None,
  dtype = None 
)
Inverse FFT and crop padding from image array.

Parameters
----------
imgArr : `numpy.ndarray` of `numpy.complex`
    Fourier space array representing a real image.

origSize : `tuple` of `int`
    Original unpadded shape tuple of the image to be cropped to.

filtInf, filtNan : `numpy.ndarray` of bool or int, optional
    If specified, they are used as index arrays for ``result`` to set values to
    `numpy.inf` and `numpy.nan` respectively at these positions.

dtype : `numpy.dtype`, optional
    Dtype of result array to cast return values to implicitly. This is to
    spare one array copy operation at reducing double precision to single.
    If `None` result inherits dtype of `imgArr`.

Returns
-------
result : `numpy.ndarray` of `dtype`

Definition at line 528 of file zogy.py.

528  def inverseFftAndCropImage(self, imgArr, origSize, filtInf=None, filtNaN=None, dtype=None):
529  """Inverse FFT and crop padding from image array.
530 
531  Parameters
532  ----------
533  imgArr : `numpy.ndarray` of `numpy.complex`
534  Fourier space array representing a real image.
535 
536  origSize : `tuple` of `int`
537  Original unpadded shape tuple of the image to be cropped to.
538 
539  filtInf, filtNan : `numpy.ndarray` of bool or int, optional
540  If specified, they are used as index arrays for ``result`` to set values to
541  `numpy.inf` and `numpy.nan` respectively at these positions.
542 
543  dtype : `numpy.dtype`, optional
544  Dtype of result array to cast return values to implicitly. This is to
545  spare one array copy operation at reducing double precision to single.
546  If `None` result inherits dtype of `imgArr`.
547 
548  Returns
549  -------
550  result : `numpy.ndarray` of `dtype`
551  """
552  imgNew = np.fft.ifft2(imgArr)
553  imgNew = imgNew.real
554  imgNew = self.padCenterOriginArray(imgNew, origSize, useInverse=True, dtype=dtype)
555  if filtInf is not None:
556  imgNew[filtInf] = np.inf
557  if filtNaN is not None:
558  imgNew[filtNaN] = np.nan
559  return imgNew
560 

◆ makeKernelPsfFromArray()

def lsst.ip.diffim.zogy.ZogyTask.makeKernelPsfFromArray (   A)
static
Create a non spatially varying PSF from a `numpy.ndarray`.

Parameters
----------
A : `numpy.ndarray`
    2D array to use as the new psf image. The pixels are copied.

Returns
-------
psfNew : `lsst.meas.algorithms.KernelPsf`
    The constructed PSF.

Definition at line 998 of file zogy.py.

998  def makeKernelPsfFromArray(A):
999  """Create a non spatially varying PSF from a `numpy.ndarray`.
1000 
1001  Parameters
1002  ----------
1003  A : `numpy.ndarray`
1004  2D array to use as the new psf image. The pixels are copied.
1005 
1006  Returns
1007  -------
1008  psfNew : `lsst.meas.algorithms.KernelPsf`
1009  The constructed PSF.
1010  """
1011  psfImg = afwImage.ImageD(A.astype(np.float64, copy=True), deep=False)
1012  psfNew = measAlg.KernelPsf(afwMath.FixedKernel(psfImg))
1013  return psfNew
1014 
A kernel created from an Image.
Definition: Kernel.h:471

◆ makeSpatialPsf()

def lsst.ip.diffim.zogy.ZogyTask.makeSpatialPsf (   self,
  gridPsfs 
)
Construct a CoaddPsf based on PSFs from individual sub image solutions.

Parameters
----------
gridPsfs : iterable of `lsst.pipe.base.Struct`
    Iterable of bounding boxes (``bbox``) and Psf solutions (``psf``).

Returns
-------
psf : `lsst.meas.algorithms.CoaddPsf`
    A psf constructed from the PSFs of the individual subExposures.

Definition at line 437 of file zogy.py.

437  def makeSpatialPsf(self, gridPsfs):
438  """Construct a CoaddPsf based on PSFs from individual sub image solutions.
439 
440  Parameters
441  ----------
442  gridPsfs : iterable of `lsst.pipe.base.Struct`
443  Iterable of bounding boxes (``bbox``) and Psf solutions (``psf``).
444 
445  Returns
446  -------
447  psf : `lsst.meas.algorithms.CoaddPsf`
448  A psf constructed from the PSFs of the individual subExposures.
449  """
450  schema = afwTable.ExposureTable.makeMinimalSchema()
451  schema.addField("weight", type="D", doc="Coadd weight")
452  mycatalog = afwTable.ExposureCatalog(schema)
453 
454  # We're just using the exposure's WCS (assuming that the subExposures'
455  # WCSs are the same, which they better be!).
456  wcsref = self.fullExp1.getWcs()
457  for i, res in enumerate(gridPsfs):
458  record = mycatalog.getTable().makeRecord()
459  record.setPsf(res.psf)
460  record.setWcs(wcsref)
461  record.setBBox(res.bbox)
462  record['weight'] = 1.0
463  record['id'] = i
464  mycatalog.append(record)
465 
466  # create the CoaddPsf
467  psf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight')
468  return psf
469 
Custom catalog class for ExposureRecord/Table.
Definition: Exposure.h:311

◆ padAndFftImage()

def lsst.ip.diffim.zogy.ZogyTask.padAndFftImage (   self,
  imgArr 
)
Prepare and forward FFT an image array.

Parameters
----------
imgArr : `numpy.ndarray` of `float`
    Original array. In-place modified as `numpy.nan` and `numpy.inf` are replaced by
    array mean.

Returns
-------
result : `lsst.pipe.base.Struct`
    - ``imFft`` : `numpy.ndarray` of `numpy.complex`.
        FFT of image.
    - ``filtInf``, ``filtNaN`` : `numpy.ndarray` of `bool`

Notes
-----
Save location of non-finite values for restoration, and replace them
with image mean values. Re-center and zero pad array by `padCenterOriginArray`.

Definition at line 470 of file zogy.py.

470  def padAndFftImage(self, imgArr):
471  """Prepare and forward FFT an image array.
472 
473  Parameters
474  ----------
475  imgArr : `numpy.ndarray` of `float`
476  Original array. In-place modified as `numpy.nan` and `numpy.inf` are replaced by
477  array mean.
478 
479  Returns
480  -------
481  result : `lsst.pipe.base.Struct`
482  - ``imFft`` : `numpy.ndarray` of `numpy.complex`.
483  FFT of image.
484  - ``filtInf``, ``filtNaN`` : `numpy.ndarray` of `bool`
485 
486  Notes
487  -----
488  Save location of non-finite values for restoration, and replace them
489  with image mean values. Re-center and zero pad array by `padCenterOriginArray`.
490  """
491  filtInf = np.isinf(imgArr)
492  filtNaN = np.isnan(imgArr)
493  imgArr[filtInf] = np.nan
494  imgArr[filtInf | filtNaN] = np.nanmean(imgArr)
495  self.log.debug("Replacing %s Inf and %s NaN values.",
496  np.sum(filtInf), np.sum(filtNaN))
497  imgArr = self.padCenterOriginArray(imgArr, self.freqSpaceShape)
498  imgArr = np.fft.fft2(imgArr)
499  return pipeBase.Struct(imFft=imgArr, filtInf=filtInf, filtNaN=filtNaN)
500 

◆ padCenterOriginArray()

def lsst.ip.diffim.zogy.ZogyTask.padCenterOriginArray (   A,
  newShape,
  useInverse = False,
  dtype = None 
)
static
Zero pad an image where the origin is at the center and replace the
origin to the corner as required by the periodic input of FFT.

Implement also the inverse operation, crop the padding and re-center data.

Parameters
----------
A : `numpy.ndarray`
    An array to copy from.
newShape : `tuple` of `int`
    The dimensions of the resulting array. For padding, the resulting array
    must be larger than A in each dimension. For the inverse operation this
    must be the original, before padding dimensions of the array.
useInverse : bool, optional
    Selector of forward, add padding, operation (False)
    or its inverse, crop padding, operation (True).
dtype: `numpy.dtype`, optional
    Dtype of output array. Values must be implicitly castable to this type.
    Use to get expected result type, e.g. single float (nympy.float32).
    If not specified, dtype is inherited from ``A``.

Returns
-------
R : `numpy.ndarray`
    The padded or unpadded array with shape of `newShape` and dtype of ``dtype``.

Notes
-----
For odd dimensions, the splitting is rounded to
put the center pixel into the new corner origin (0,0). This is to be consistent
e.g. for a dirac delta kernel that is originally located at the center pixel.


Raises
------
ValueError : ``newShape`` dimensions must be greater than or equal to the
    dimensions of ``A`` for the forward operation and less than or equal to
    for the inverse operation.

Definition at line 142 of file zogy.py.

142  def padCenterOriginArray(A, newShape, useInverse=False, dtype=None):
143  """Zero pad an image where the origin is at the center and replace the
144  origin to the corner as required by the periodic input of FFT.
145 
146  Implement also the inverse operation, crop the padding and re-center data.
147 
148  Parameters
149  ----------
150  A : `numpy.ndarray`
151  An array to copy from.
152  newShape : `tuple` of `int`
153  The dimensions of the resulting array. For padding, the resulting array
154  must be larger than A in each dimension. For the inverse operation this
155  must be the original, before padding dimensions of the array.
156  useInverse : bool, optional
157  Selector of forward, add padding, operation (False)
158  or its inverse, crop padding, operation (True).
159  dtype: `numpy.dtype`, optional
160  Dtype of output array. Values must be implicitly castable to this type.
161  Use to get expected result type, e.g. single float (nympy.float32).
162  If not specified, dtype is inherited from ``A``.
163 
164  Returns
165  -------
166  R : `numpy.ndarray`
167  The padded or unpadded array with shape of `newShape` and dtype of ``dtype``.
168 
169  Notes
170  -----
171  For odd dimensions, the splitting is rounded to
172  put the center pixel into the new corner origin (0,0). This is to be consistent
173  e.g. for a dirac delta kernel that is originally located at the center pixel.
174 
175 
176  Raises
177  ------
178  ValueError : ``newShape`` dimensions must be greater than or equal to the
179  dimensions of ``A`` for the forward operation and less than or equal to
180  for the inverse operation.
181  """
182 
183  # The forward and inverse operations should round odd dimension halves at the opposite
184  # sides to get the pixels back to their original positions.
185  if not useInverse:
186  # Forward operation: First and second halves with respect to the axes of A.
187  firstHalves = [x//2 for x in A.shape]
188  secondHalves = [x-y for x, y in zip(A.shape, firstHalves)]
189  for d1, d2 in zip(newShape, A.shape):
190  if d1 < d2:
191  raise ValueError("Newshape dimensions must be greater or equal")
192  else:
193  # Inverse operation: Opposite rounding
194  secondHalves = [x//2 for x in newShape]
195  firstHalves = [x-y for x, y in zip(newShape, secondHalves)]
196  for d1, d2 in zip(newShape, A.shape):
197  if d1 > d2:
198  raise ValueError("Newshape dimensions must be smaller or equal")
199 
200  if dtype is None:
201  dtype = A.dtype
202 
203  R = np.zeros(newShape, dtype=dtype)
204  R[-firstHalves[0]:, -firstHalves[1]:] = A[:firstHalves[0], :firstHalves[1]]
205  R[:secondHalves[0], -firstHalves[1]:] = A[-secondHalves[0]:, :firstHalves[1]]
206  R[:secondHalves[0], :secondHalves[1]] = A[-secondHalves[0]:, -secondHalves[1]:]
207  R[-firstHalves[0]:, :secondHalves[1]] = A[:firstHalves[0], -secondHalves[1]:]
208  return R
209 

◆ pasteSubDiffImg()

def lsst.ip.diffim.zogy.ZogyTask.pasteSubDiffImg (   self,
  ftDiff,
  diffExp,
  scoreExp = None 
)
Paste sub image results back into result Exposure objects.

Parameters
----------
ftDiff : `lsst.pipe.base.Struct`
    Result struct by `calculateFourierDiffim`.
diffExp : `lsst.afw.image.Exposure`
    The result exposure to paste into the sub image result.
    Must be dimensions and dtype of ``self.fullExp1``.
scoreExp : `lsst.afw.image.Exposure` or `None`
    The result score exposure to paste into the sub image result.
    Must be dimensions and dtype of ``self.fullExp1``.
    If `None`, the score image results are disregarded.

Returns
-------
None

Notes
-----
The PSF of the score image is just to make the score image resemble a
regular exposure and to study the algorithm performance.

Add an entry to the ``self.gridPsfs`` list.

gridPsfs : `list` of `lsst.pipe.base.Struct`
    - ``bbox`` : `lsst.geom.Box2I`
        The inner region of the grid cell.
    - ``Pd`` :  `lsst.meas.algorithms.KernelPsf`
        The diffim PSF in this cell.
    - ``Ps`` :  `lsst.meas.algorithms.KernelPsf` or `None`
        The score image PSF in this cell or `None` if the score
        image was not calculated.

Definition at line 1015 of file zogy.py.

1015  def pasteSubDiffImg(self, ftDiff, diffExp, scoreExp=None):
1016  """Paste sub image results back into result Exposure objects.
1017 
1018  Parameters
1019  ----------
1020  ftDiff : `lsst.pipe.base.Struct`
1021  Result struct by `calculateFourierDiffim`.
1022  diffExp : `lsst.afw.image.Exposure`
1023  The result exposure to paste into the sub image result.
1024  Must be dimensions and dtype of ``self.fullExp1``.
1025  scoreExp : `lsst.afw.image.Exposure` or `None`
1026  The result score exposure to paste into the sub image result.
1027  Must be dimensions and dtype of ``self.fullExp1``.
1028  If `None`, the score image results are disregarded.
1029 
1030  Returns
1031  -------
1032  None
1033 
1034  Notes
1035  -----
1036  The PSF of the score image is just to make the score image resemble a
1037  regular exposure and to study the algorithm performance.
1038 
1039  Add an entry to the ``self.gridPsfs`` list.
1040 
1041  gridPsfs : `list` of `lsst.pipe.base.Struct`
1042  - ``bbox`` : `lsst.geom.Box2I`
1043  The inner region of the grid cell.
1044  - ``Pd`` : `lsst.meas.algorithms.KernelPsf`
1045  The diffim PSF in this cell.
1046  - ``Ps`` : `lsst.meas.algorithms.KernelPsf` or `None`
1047  The score image PSF in this cell or `None` if the score
1048  image was not calculated.
1049  """
1050  D = self.inverseFftAndCropImage(
1051  ftDiff.D, self.freqSpaceShape, dtype=self.fullExp1.image.dtype)
1052  varPlaneD = self.inverseFftAndCropImage(
1053  ftDiff.varPlaneD, self.freqSpaceShape, dtype=self.fullExp1.variance.dtype)
1054  Pd = self.inverseFftAndCropImage(
1055  ftDiff.Pd, self.psfShape1, dtype=self.subExpPsf1.dtype)
1056  sumPd = np.sum(Pd)
1057  # If this is smaller than 1. it is an indicator that it does not fit its original dimensions
1058  self.log.info("Pd sum before normalization: %.3f", sumPd)
1059  Pd /= sumPd
1060  # Convert Pd into a Psf object
1061  Pd = self.makeKernelPsfFromArray(Pd)
1062 
1063  xy0 = self.cutBoxes1.outerBox.getMin()
1064  # D is already converted back to dtype of fullExp1
1065  # Encapsulate D simply into an afwImage.Image for correct inner-outer box handling
1066  imgD = afwImage.Image(D, deep=False, xy0=xy0, dtype=self.fullExp1.image.dtype)
1067  diffExp.image[self.cutBoxes1.innerBox] = imgD[self.cutBoxes1.innerBox]
1068  imgVarPlaneD = afwImage.Image(varPlaneD, deep=False, xy0=xy0,
1069  dtype=self.fullExp1.variance.dtype)
1070  diffExp.variance[self.cutBoxes1.innerBox] = imgVarPlaneD[self.cutBoxes1.innerBox]
1071  diffExp.mask[self.cutBoxes1.innerBox] = self.calculateMaskPlane(
1072  self.fullExp1.mask[self.cutBoxes1.innerBox], self.fullExp2.mask[self.cutBoxes2.innerBox])
1073 
1074  # Calibrate the image; subimages on the grid must be on the same photometric scale
1075  # Now the calibration object will be 1. everywhere
1076  diffExp.maskedImage[self.cutBoxes1.innerBox] /= ftDiff.Fd
1077 
1078  if ftDiff.S is not None and scoreExp is not None:
1079  S = self.inverseFftAndCropImage(
1080  ftDiff.S, self.freqSpaceShape, dtype=self.fullExp1.image.dtype)
1081  varPlaneS = self.inverseFftAndCropImage(
1082  ftDiff.varPlaneS, self.freqSpaceShape, dtype=self.fullExp1.variance.dtype)
1083 
1084  imgS = afwImage.Image(S, deep=False, xy0=xy0, dtype=self.fullExp1.image.dtype)
1085  imgVarPlaneS = afwImage.Image(varPlaneS, deep=False, xy0=xy0,
1086  dtype=self.fullExp1.variance.dtype)
1087  scoreExp.image[self.cutBoxes1.innerBox] = imgS[self.cutBoxes1.innerBox]
1088  scoreExp.variance[self.cutBoxes1.innerBox] = imgVarPlaneS[self.cutBoxes1.innerBox]
1089 
1090  # PSF of S
1091  Ps = self.inverseFftAndCropImage(ftDiff.Ps, self.psfShape1, dtype=self.subExpPsf1.dtype)
1092  sumPs = np.sum(Ps)
1093  self.log.info("Ps sum before normalization: %.3f", sumPs)
1094  Ps /= sumPs
1095 
1096  # TODO DM-23855 : Additional score image corrections may be done here
1097 
1098  scoreExp.mask[self.cutBoxes1.innerBox] = diffExp.mask[self.cutBoxes1.innerBox]
1099  # Convert Ps into a Psf object
1100  Ps = self.makeKernelPsfFromArray(Ps)
1101  else:
1102  Ps = None
1103  self.gridPsfs.append(pipeBase.Struct(bbox=self.cutBoxes1.innerBox, Pd=Pd, Ps=Ps))
1104 
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:51
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33

◆ pixelSpaceSquare()

def lsst.ip.diffim.zogy.ZogyTask.pixelSpaceSquare (   D)
static
Square the argument in pixel space.

Parameters
----------
D : 2D `numpy.ndarray` of `numpy.complex`
    Fourier transform of a real valued array.

Returns
-------
R : `numpy.ndarray` of `numpy.complex`

Notes
-----
``D`` is to be inverse Fourier transformed, squared and then
forward Fourier transformed again, i.e. an autoconvolution in Fourier space.
This operation is not distributive over multiplication.
``pixelSpaceSquare(A*B) != pixelSpaceSquare(A)*pixelSpaceSquare(B)``

Definition at line 773 of file zogy.py.

773  def pixelSpaceSquare(D):
774  """Square the argument in pixel space.
775 
776  Parameters
777  ----------
778  D : 2D `numpy.ndarray` of `numpy.complex`
779  Fourier transform of a real valued array.
780 
781  Returns
782  -------
783  R : `numpy.ndarray` of `numpy.complex`
784 
785  Notes
786  -----
787  ``D`` is to be inverse Fourier transformed, squared and then
788  forward Fourier transformed again, i.e. an autoconvolution in Fourier space.
789  This operation is not distributive over multiplication.
790  ``pixelSpaceSquare(A*B) != pixelSpaceSquare(A)*pixelSpaceSquare(B)``
791  """
792  R = np.real(np.fft.ifft2(D))
793  R *= R
794  R = np.fft.fft2(R)
795  return R
796 

◆ prepareFullExposure()

def lsst.ip.diffim.zogy.ZogyTask.prepareFullExposure (   self,
  exposure1,
  exposure2,
  correctBackground = False 
)
Performs calculations that apply to the full exposures once only.

Parameters
----------

exposure1, exposure2 : `lsst.afw.image.Exposure`
    The input exposures. Copies are made for internal calculations.

correctBackground : `bool`, optional
    If True, subtracts sigma-clipped mean of exposures. The algorithm
    assumes zero expectation value at background pixels.

Returns
-------
None

Notes
-----
Set a number of instance fields with pre-calculated values.

Raises
------
ValueError : If photometric calibrations are not available while
    ``config.scaleByCalibration`` equals True.

Definition at line 614 of file zogy.py.

614  def prepareFullExposure(self, exposure1, exposure2, correctBackground=False):
615  """Performs calculations that apply to the full exposures once only.
616 
617  Parameters
618  ----------
619 
620  exposure1, exposure2 : `lsst.afw.image.Exposure`
621  The input exposures. Copies are made for internal calculations.
622 
623  correctBackground : `bool`, optional
624  If True, subtracts sigma-clipped mean of exposures. The algorithm
625  assumes zero expectation value at background pixels.
626 
627  Returns
628  -------
629  None
630 
631  Notes
632  -----
633  Set a number of instance fields with pre-calculated values.
634 
635  Raises
636  ------
637  ValueError : If photometric calibrations are not available while
638  ``config.scaleByCalibration`` equals True.
639  """
640  self.statsControl = afwMath.StatisticsControl()
641  self.statsControl.setNumSigmaClip(3.)
642  self.statsControl.setNumIter(3)
643  self.statsControl.setAndMask(afwImage.Mask.getPlaneBitMask(
644  self.config.ignoreMaskPlanes))
645 
646  exposure1 = exposure1.clone()
647  exposure2 = exposure2.clone()
648  # Fallback values if sub exposure variance calculation is problematic
649  sig1 = np.sqrt(self._computeVarianceMean(exposure1))
650  self.fullExpVar1 = sig1*sig1
651  sig2 = np.sqrt(self._computeVarianceMean(exposure2))
652  self.fullExpVar2 = sig2*sig2
653 
654  # If 'scaleByCalibration' is True then these norms are overwritten
655  if self.config.scaleByCalibration:
656  calibObj1 = exposure1.getPhotoCalib()
657  calibObj2 = exposure2.getPhotoCalib()
658  if calibObj1 is None or calibObj2 is None:
659  raise ValueError("Photometric calibrations are not available for both exposures.")
660  mImg1 = calibObj1.calibrateImage(exposure1.maskedImage)
661  mImg2 = calibObj2.calibrateImage(exposure2.maskedImage)
662  self.F1 = 1.
663  self.F2 = 1.
664  else:
665  self.F1 = self.config.templateFluxScaling # default is 1
666  self.F2 = self.config.scienceFluxScaling # default is 1
667  mImg1 = exposure1.maskedImage
668  mImg2 = exposure2.maskedImage
669 
670  # mImgs can be in-place modified
671  if correctBackground:
672  self.subtractImageMean(mImg1.image, mImg1.mask, self.statsControl)
673  self.subtractImageMean(mImg2.image, mImg2.mask, self.statsControl)
674 
675  # Determine border size
676  self.borderSize = self.estimateMatchingKernelSize(exposure1.getPsf(), exposure2.getPsf())
677  self.log.debug("Minimum padding border size: %s pixels", self.borderSize)
678  # Remove non-finite values from the images in-place
679  self.filtsImg1 = self.removeNonFinitePixels(mImg1.image.array)
680  self.filtsImg2 = self.removeNonFinitePixels(mImg2.image.array)
681  self.filtsVar1 = self.removeNonFinitePixels(mImg1.variance.array)
682  self.filtsVar2 = self.removeNonFinitePixels(mImg2.variance.array)
683 
684  exposure1.maskedImage = mImg1
685  exposure2.maskedImage = mImg2
686 
687  self.fullExp1 = exposure1
688  self.fullExp2 = exposure2
689 
Pass parameters to a Statistics object.
Definition: Statistics.h:92

◆ prepareSubExposure()

def lsst.ip.diffim.zogy.ZogyTask.prepareSubExposure (   self,
  localCutout,
  psf1 = None,
  psf2 = None,
  sig1 = None,
  sig2 = None 
)
Perform per-sub exposure preparations.

Parameters
----------
sig1, sig2 : `float`, optional
    For debug purposes only, copnsider that the image
    may already be rescaled by the photometric calibration.
localCutout : `lsst.pipe.base.Struct`
    - innerBox, outerBox: `lsst.geom.Box2I` LOCAL inner and outer boxes
psf1, psf2 : `lsst.afw.detection.Psf`, optional
    If specified, use given psf as the sub exposure psf. For debug purposes.
sig1, sig2 : `float`, optional
    If specified, use value as the sub-exposures' background noise sigma value.

Returns
-------
None

Definition at line 690 of file zogy.py.

690  def prepareSubExposure(self, localCutout, psf1=None, psf2=None, sig1=None, sig2=None):
691  """Perform per-sub exposure preparations.
692 
693  Parameters
694  ----------
695  sig1, sig2 : `float`, optional
696  For debug purposes only, copnsider that the image
697  may already be rescaled by the photometric calibration.
698  localCutout : `lsst.pipe.base.Struct`
699  - innerBox, outerBox: `lsst.geom.Box2I` LOCAL inner and outer boxes
700  psf1, psf2 : `lsst.afw.detection.Psf`, optional
701  If specified, use given psf as the sub exposure psf. For debug purposes.
702  sig1, sig2 : `float`, optional
703  If specified, use value as the sub-exposures' background noise sigma value.
704 
705  Returns
706  -------
707  None
708 
709  """
710  self.log.debug("Processing LOCAL cell w/ inner box:%s, outer box:%s",
711  localCutout.innerBox, localCutout.outerBox)
712  # The PARENT origin cutout boxes for the two exposures
713  self.cutBoxes1 = pipeBase.Struct(
714  innerBox=localCutout.innerBox.shiftedBy(Extent2I(self.fullExp1.getXY0())),
715  outerBox=localCutout.outerBox.shiftedBy(Extent2I(self.fullExp1.getXY0())))
716  self.cutBoxes2 = pipeBase.Struct(
717  innerBox=localCutout.innerBox.shiftedBy(Extent2I(self.fullExp2.getXY0())),
718  outerBox=localCutout.outerBox.shiftedBy(Extent2I(self.fullExp2.getXY0())))
719  # The sub-exposure views of the useful inner area of this grid cell
720  innerSubExp1 = self.fullExp1[self.cutBoxes1.innerBox]
721  innerSubExp2 = self.fullExp2[self.cutBoxes2.innerBox]
722  if psf1 is None:
723  self.subExpPsf1 = self.computePsfAtCenter(innerSubExp1)
724  else:
725  self.subExpPsf1 = psf1
726  if psf2 is None:
727  self.subExpPsf2 = self.computePsfAtCenter(innerSubExp2)
728  else:
729  self.subExpPsf2 = psf2
730  self.checkCentroids(self.subExpPsf1.array, self.subExpPsf2.array)
731  psfBBox1 = self.subExpPsf1.getBBox()
732  psfBBox2 = self.subExpPsf2.getBBox()
733  self.psfShape1 = (psfBBox1.getHeight(), psfBBox1.getWidth())
734  self.psfShape2 = (psfBBox2.getHeight(), psfBBox2.getWidth())
735  # sig1 and sig2 should not be set externally, just for debug purpose
736  if sig1 is None:
737  sig1 = np.sqrt(self._computeVarianceMean(innerSubExp1))
738  if sig1 > 0.: # Not zero and not nan
739  self.subExpVar1 = sig1*sig1
740  else:
741  self.subExpVar1 = self.fullExpVar1
742  if sig2 is None:
743  sig2 = np.sqrt(self._computeVarianceMean(innerSubExp2))
744  if sig2 > 0.: # Not zero and not nan
745  self.subExpVar2 = sig2*sig2
746  else:
747  self.subExpVar2 = self.fullExpVar2
748  self.freqSpaceShape = (localCutout.outerBox.getHeight(), localCutout.outerBox.getWidth())
749 
750  self.subImg1 = self.initializeSubImage(
751  self.fullExp1, self.cutBoxes1.innerBox, self.cutBoxes1.outerBox,
752  self.subExpVar1, useNoise=True)
753  self.subImg2 = self.initializeSubImage(
754  self.fullExp2, self.cutBoxes2.innerBox, self.cutBoxes2.outerBox,
755  self.subExpVar2, useNoise=True)
756 
757  D = self.padCenterOriginArray(self.subImg1.image.array, self.freqSpaceShape)
758  self.subImgFft1 = np.fft.fft2(D)
759  D = self.padCenterOriginArray(self.subImg1.variance.array, self.freqSpaceShape)
760  self.subVarImgFft1 = np.fft.fft2(D)
761 
762  D = self.padCenterOriginArray(self.subImg2.image.array, self.freqSpaceShape)
763  self.subImgFft2 = np.fft.fft2(D)
764  D = self.padCenterOriginArray(self.subImg2.variance.array, self.freqSpaceShape)
765  self.subVarImgFft2 = np.fft.fft2(D)
766 
767  D = self.padCenterOriginArray(self.subExpPsf1.array, self.freqSpaceShape)
768  self.psfFft1 = np.fft.fft2(D)
769  D = self.padCenterOriginArray(self.subExpPsf2.array, self.freqSpaceShape)
770  self.psfFft2 = np.fft.fft2(D)
771 

◆ removeNonFinitePixels()

def lsst.ip.diffim.zogy.ZogyTask.removeNonFinitePixels (   self,
  imgArr 
)
Replace non-finite pixel values in-place.

Save the locations of non-finite values for restoration, and replace them
with image mean values.

Parameters
----------
imgArr : `numpy.ndarray` of `float`
    The image array. Non-finite values are replaced in-place in this array.

Returns
-------
result : `lsst.pipe.base.Struct`
    - ``filtInf``, ``filtNaN`` : `numpy.ndarray` of `bool`
        The filter of the pixel values that were inf or nan.

Definition at line 501 of file zogy.py.

501  def removeNonFinitePixels(self, imgArr):
502  """Replace non-finite pixel values in-place.
503 
504  Save the locations of non-finite values for restoration, and replace them
505  with image mean values.
506 
507  Parameters
508  ----------
509  imgArr : `numpy.ndarray` of `float`
510  The image array. Non-finite values are replaced in-place in this array.
511 
512  Returns
513  -------
514  result : `lsst.pipe.base.Struct`
515  - ``filtInf``, ``filtNaN`` : `numpy.ndarray` of `bool`
516  The filter of the pixel values that were inf or nan.
517  """
518  filtInf = np.isinf(imgArr)
519  filtNaN = np.isnan(imgArr)
520  # Masked edge and bad pixels could also be removed here in the same way
521  # in the future
522  imgArr[filtInf] = np.nan
523  imgArr[filtInf | filtNaN] = np.nanmean(imgArr)
524  self.log.debug("Replacing %s Inf and %s NaN values.",
525  np.sum(filtInf), np.sum(filtNaN))
526  return pipeBase.Struct(filtInf=filtInf, filtNaN=filtNaN)
527 

◆ run()

def lsst.ip.diffim.zogy.ZogyTask.run (   self,
  exposure1,
  exposure2,
  calculateScore = True 
)
Task entry point to perform the zogy subtraction
of ``exposure1-exposure2``.

Parameters
----------
exposure1, exposure2 : `lsst.afw.image.Exposure`
    Two exposures warped and matched into matching pixel dimensions.
calculateScore : `bool`, optional
    If True (default), calculate the score image and return in ``scoreExp``.


Returns
-------
resultName : `lsst.pipe.base.Struct`
    - ``diffExp`` : `lsst.afw.image.Exposure`
        The Zogy difference exposure (``exposure1-exposure2``).
    - ``scoreExp`` : `lsst.afw.image.Exposure` or `None`
        The Zogy significance or score (S) exposure if ``calculateScore==True``.
    - ``ftDiff`` : `lsst.pipe.base.Struct`
        Lower level return struct by `calculateFourierDiffim` with added
        fields from the task instance. For debug purposes.

Notes
-----

``diffExp`` and ``scoreExp`` always inherit their metadata from
``exposure1`` (e.g. dtype, bbox, wcs).

The score image (``S``) is defined in the ZOGY paper as the detection
statistic value at each pixel. In the ZOGY image model, the input images
have uniform variance noises and thus ``S`` has uniform per pixel
variance (though it is not scaled to 1). In Section 3.3 of the paper,
there are "corrections" defined to the score image to correct the
significance values for some deviations from the image model. The first
of these corrections is the calculation of the *variance plane* of ``S``
allowing for different per pixel variance values by following the
overall convolution operation on the pixels of the input images. ``S``
scaled (divided) by its corrected per pixel noise is referred as
``Scorr`` in the paper.

In the current implementation, ``scoreExp`` contains ``S`` in its image
plane and the calculated (non-uniform) variance plane of ``S`` in its
variance plane. ``scoreExp`` can be used directly for source detection
as a likelihood image by respecting its variance plane or can be divided
by the square root of the variance plane to scale detection significance
values into units of sigma. ``S`` should be interpreted as a detection
likelihood directly on a per-pixel basis. The calculated PSF
of ``S`` is merely an indication how much the input PSFs localize point
sources.

TODO DM-23855 : Implement further correction tags to the variance of
``scoreExp``. As of DM-25174 it is not determined how important these
further correction tags are.

Definition at line 1161 of file zogy.py.

1161  def run(self, exposure1, exposure2, calculateScore=True):
1162  """Task entry point to perform the zogy subtraction
1163  of ``exposure1-exposure2``.
1164 
1165  Parameters
1166  ----------
1167  exposure1, exposure2 : `lsst.afw.image.Exposure`
1168  Two exposures warped and matched into matching pixel dimensions.
1169  calculateScore : `bool`, optional
1170  If True (default), calculate the score image and return in ``scoreExp``.
1171 
1172 
1173  Returns
1174  -------
1175  resultName : `lsst.pipe.base.Struct`
1176  - ``diffExp`` : `lsst.afw.image.Exposure`
1177  The Zogy difference exposure (``exposure1-exposure2``).
1178  - ``scoreExp`` : `lsst.afw.image.Exposure` or `None`
1179  The Zogy significance or score (S) exposure if ``calculateScore==True``.
1180  - ``ftDiff`` : `lsst.pipe.base.Struct`
1181  Lower level return struct by `calculateFourierDiffim` with added
1182  fields from the task instance. For debug purposes.
1183 
1184  Notes
1185  -----
1186 
1187  ``diffExp`` and ``scoreExp`` always inherit their metadata from
1188  ``exposure1`` (e.g. dtype, bbox, wcs).
1189 
1190  The score image (``S``) is defined in the ZOGY paper as the detection
1191  statistic value at each pixel. In the ZOGY image model, the input images
1192  have uniform variance noises and thus ``S`` has uniform per pixel
1193  variance (though it is not scaled to 1). In Section 3.3 of the paper,
1194  there are "corrections" defined to the score image to correct the
1195  significance values for some deviations from the image model. The first
1196  of these corrections is the calculation of the *variance plane* of ``S``
1197  allowing for different per pixel variance values by following the
1198  overall convolution operation on the pixels of the input images. ``S``
1199  scaled (divided) by its corrected per pixel noise is referred as
1200  ``Scorr`` in the paper.
1201 
1202  In the current implementation, ``scoreExp`` contains ``S`` in its image
1203  plane and the calculated (non-uniform) variance plane of ``S`` in its
1204  variance plane. ``scoreExp`` can be used directly for source detection
1205  as a likelihood image by respecting its variance plane or can be divided
1206  by the square root of the variance plane to scale detection significance
1207  values into units of sigma. ``S`` should be interpreted as a detection
1208  likelihood directly on a per-pixel basis. The calculated PSF
1209  of ``S`` is merely an indication how much the input PSFs localize point
1210  sources.
1211 
1212  TODO DM-23855 : Implement further correction tags to the variance of
1213  ``scoreExp``. As of DM-25174 it is not determined how important these
1214  further correction tags are.
1215  """
1216  # We use the dimensions of the 1st image only in the code
1217  if exposure1.getDimensions() != exposure2.getDimensions():
1218  raise ValueError("Exposure dimensions do not match ({} != {} )".format(
1219  exposure1.getDimensions(), exposure2.getDimensions()))
1220 
1221  self.prepareFullExposure(exposure1, exposure2, correctBackground=self.config.correctBackground)
1222  # Do not use the exposure1, exposure2 input arguments from here
1223  exposure1 = None
1224  exposure2 = None
1225  if self.config.doSpatialGrid:
1226  gridBoxes = self.generateGrid(
1227  self.fullExp1.getBBox(ImageOrigin.LOCAL), Extent2I(self.borderSize, self.borderSize),
1228  Extent2I(Extent2I(self.borderSize, self.borderSize) * self.config.gridInnerSize),
1229  powerOfTwo=True)
1230  else:
1231  gridBoxes = self.generateGrid(
1232  self.fullExp1.getBBox(ImageOrigin.LOCAL), Extent2I(self.borderSize, self.borderSize),
1233  self.fullExp1.getBBox().getDimensions(), powerOfTwo=True)
1234 
1235  diffExp = self.fullExp1.clone()
1236  if calculateScore:
1237  scoreExp = self.fullExp1.clone()
1238  else:
1239  scoreExp = None
1240  self.gridPsfs = []
1241  # Loop through grid boxes
1242  for boxPair in gridBoxes:
1243  self.prepareSubExposure(boxPair) # Extract sub images and fft
1244  ftDiff = self.calculateFourierDiffim(
1245  self.psfFft1, self.subImgFft1, self.subVarImgFft1, self.F1, self.subExpVar1,
1246  self.psfFft2, self.subImgFft2, self.subVarImgFft2, self.F2, self.subExpVar2,
1247  calculateScore=calculateScore)
1248  self.pasteSubDiffImg(ftDiff, diffExp, scoreExp) # Paste back result
1249  self.finishResultExposures(diffExp, scoreExp)
1250  # Add debug info from the task instance
1251  ftDiff.freqSpaceShape = self.freqSpaceShape # The outer shape of the last grid cell
1252  ftDiff.psfShape1 = self.psfShape1 # The psf image shape in exposure1
1253  ftDiff.psfShape2 = self.psfShape2 # The psf image shape in exposure2
1254  ftDiff.borderSize = self.borderSize # The requested padding around the inner region
1255  return pipeBase.Struct(diffExp=diffExp,
1256  scoreExp=scoreExp,
1257  ftDiff=ftDiff)
1258 
1259 
def run(self, coaddExposures, bbox, wcs)
Definition: getTemplate.py:603
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174

◆ splitBorder()

def lsst.ip.diffim.zogy.ZogyTask.splitBorder (   innerBox,
  outerBox 
)
static
Split the border area around the inner box into 8 disjunct boxes.

Parameters
----------
innerBox : `lsst.geom.Box2I`
    The inner box.
outerBox : `lsst.geom.Box2I`
    The outer box. It must be at least 1 pixel larger in each direction than the inner box.

Returns
-------
resultBoxes : `list` of 8 boxes covering the edge around innerBox

Notes
-----
The border boxes do not overlap. The border is covered counter clockwise
starting from lower left corner.

Raises
------
ValueError : If ``outerBox`` is not larger than ``innerBox``.

Definition at line 298 of file zogy.py.

298  def splitBorder(innerBox, outerBox):
299  """Split the border area around the inner box into 8 disjunct boxes.
300 
301  Parameters
302  ----------
303  innerBox : `lsst.geom.Box2I`
304  The inner box.
305  outerBox : `lsst.geom.Box2I`
306  The outer box. It must be at least 1 pixel larger in each direction than the inner box.
307 
308  Returns
309  -------
310  resultBoxes : `list` of 8 boxes covering the edge around innerBox
311 
312  Notes
313  -----
314  The border boxes do not overlap. The border is covered counter clockwise
315  starting from lower left corner.
316 
317  Raises
318  ------
319  ValueError : If ``outerBox`` is not larger than ``innerBox``.
320  """
321  innerBox = innerBox.dilatedBy(1)
322  if not outerBox.contains(innerBox):
323  raise ValueError("OuterBox must be larger by at least 1 pixel in all directions")
324 
325  # ccw sequence of corners
326  o1, o2, o3, o4 = outerBox.getCorners()
327  i1, i2, i3, i4 = innerBox.getCorners()
328  p1 = Point2I(outerBox.minX, innerBox.minY)
329  p2 = Point2I(innerBox.maxX, outerBox.minY)
330  p3 = Point2I(outerBox.maxX, innerBox.maxY)
331  p4 = Point2I(innerBox.minX, outerBox.maxY)
332 
333  # The 8 border boxes ccw starting from lower left
334  pointPairs = ((o1, i1), (i1 + Extent2I(1, 0), p2 + Extent2I(-1, 0)), (o2, i2),
335  (i2 + Extent2I(0, 1), p3 + Extent2I(0, -1)), (o3, i3),
336  (i3 + Extent2I(-1, 0), p4 + Extent2I(1, 0)), (o4, i4),
337  (i4 + Extent2I(0, -1), p1 + Extent2I(0, 1)))
338  return [Box2I(x, y, invert=True) for (x, y) in pointPairs]
339 

◆ subtractImageMean()

def lsst.ip.diffim.zogy.ZogyTask.subtractImageMean (   image,
  mask,
  statsControl 
)
static
In-place subtraction of sigma-clipped mean of the image.

Parameters
----------
image : `lsst.afw.image.Image`
    Image to manipulate. Its sigma clipped mean is in-place subtracted.

mask : `lsst.afw.image.Mask`
    Mask to use for ignoring pixels.

statsControl : `lsst.afw.math.StatisticsControl`
    Config of sigma clipped mean statistics calculation.

Returns
-------
None

Raises
------
ValueError : If image mean is nan.

Definition at line 584 of file zogy.py.

584  def subtractImageMean(image, mask, statsControl):
585  """In-place subtraction of sigma-clipped mean of the image.
586 
587  Parameters
588  ----------
589  image : `lsst.afw.image.Image`
590  Image to manipulate. Its sigma clipped mean is in-place subtracted.
591 
592  mask : `lsst.afw.image.Mask`
593  Mask to use for ignoring pixels.
594 
595  statsControl : `lsst.afw.math.StatisticsControl`
596  Config of sigma clipped mean statistics calculation.
597 
598  Returns
599  -------
600  None
601 
602  Raises
603  ------
604  ValueError : If image mean is nan.
605  """
606  statObj = afwMath.makeStatistics(image, mask,
607  afwMath.MEANCLIP, statsControl)
608  mean = statObj.getValue(afwMath.MEANCLIP)
609  if not np.isnan(mean):
610  image -= mean
611  else:
612  raise ValueError("Image mean is NaN.")
613 
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition: Statistics.h:359

Member Data Documentation

◆ borderSize

lsst.ip.diffim.zogy.ZogyTask.borderSize

Definition at line 676 of file zogy.py.

◆ ConfigClass

lsst.ip.diffim.zogy.ZogyTask.ConfigClass = ZogyConfig
static

Definition at line 129 of file zogy.py.

◆ cutBoxes1

lsst.ip.diffim.zogy.ZogyTask.cutBoxes1

Definition at line 713 of file zogy.py.

◆ cutBoxes2

lsst.ip.diffim.zogy.ZogyTask.cutBoxes2

Definition at line 716 of file zogy.py.

◆ F1

lsst.ip.diffim.zogy.ZogyTask.F1

Definition at line 662 of file zogy.py.

◆ F2

lsst.ip.diffim.zogy.ZogyTask.F2

Definition at line 663 of file zogy.py.

◆ filtsImg1

lsst.ip.diffim.zogy.ZogyTask.filtsImg1

Definition at line 679 of file zogy.py.

◆ filtsImg2

lsst.ip.diffim.zogy.ZogyTask.filtsImg2

Definition at line 680 of file zogy.py.

◆ filtsVar1

lsst.ip.diffim.zogy.ZogyTask.filtsVar1

Definition at line 681 of file zogy.py.

◆ filtsVar2

lsst.ip.diffim.zogy.ZogyTask.filtsVar2

Definition at line 682 of file zogy.py.

◆ freqSpaceShape

lsst.ip.diffim.zogy.ZogyTask.freqSpaceShape

Definition at line 748 of file zogy.py.

◆ fullExp1

lsst.ip.diffim.zogy.ZogyTask.fullExp1

Definition at line 687 of file zogy.py.

◆ fullExp2

lsst.ip.diffim.zogy.ZogyTask.fullExp2

Definition at line 688 of file zogy.py.

◆ fullExpVar1

lsst.ip.diffim.zogy.ZogyTask.fullExpVar1

Definition at line 650 of file zogy.py.

◆ fullExpVar2

lsst.ip.diffim.zogy.ZogyTask.fullExpVar2

Definition at line 652 of file zogy.py.

◆ gridPsfs

lsst.ip.diffim.zogy.ZogyTask.gridPsfs

Definition at line 1240 of file zogy.py.

◆ psfFft1

lsst.ip.diffim.zogy.ZogyTask.psfFft1

Definition at line 768 of file zogy.py.

◆ psfFft2

lsst.ip.diffim.zogy.ZogyTask.psfFft2

Definition at line 770 of file zogy.py.

◆ psfShape1

lsst.ip.diffim.zogy.ZogyTask.psfShape1

Definition at line 733 of file zogy.py.

◆ psfShape2

lsst.ip.diffim.zogy.ZogyTask.psfShape2

Definition at line 734 of file zogy.py.

◆ statsControl

lsst.ip.diffim.zogy.ZogyTask.statsControl

Definition at line 640 of file zogy.py.

◆ subExpPsf1

lsst.ip.diffim.zogy.ZogyTask.subExpPsf1

Definition at line 723 of file zogy.py.

◆ subExpPsf2

lsst.ip.diffim.zogy.ZogyTask.subExpPsf2

Definition at line 727 of file zogy.py.

◆ subExpVar1

lsst.ip.diffim.zogy.ZogyTask.subExpVar1

Definition at line 739 of file zogy.py.

◆ subExpVar2

lsst.ip.diffim.zogy.ZogyTask.subExpVar2

Definition at line 745 of file zogy.py.

◆ subImg1

lsst.ip.diffim.zogy.ZogyTask.subImg1

Definition at line 750 of file zogy.py.

◆ subImg2

lsst.ip.diffim.zogy.ZogyTask.subImg2

Definition at line 753 of file zogy.py.

◆ subImgFft1

lsst.ip.diffim.zogy.ZogyTask.subImgFft1

Definition at line 758 of file zogy.py.

◆ subImgFft2

lsst.ip.diffim.zogy.ZogyTask.subImgFft2

Definition at line 763 of file zogy.py.

◆ subVarImgFft1

lsst.ip.diffim.zogy.ZogyTask.subVarImgFft1

Definition at line 760 of file zogy.py.

◆ subVarImgFft2

lsst.ip.diffim.zogy.ZogyTask.subVarImgFft2

Definition at line 765 of file zogy.py.


The documentation for this class was generated from the following file: