LSST Applications g0da5cf3356+25b44625d0,g17e5ecfddb+50a5ac4092,g1c76d35bf8+585f0f68a2,g295839609d+8ef6456700,g2e2c1a68ba+cc1f6f037e,g38293774b4+62d12e78cb,g3b44f30a73+2891c76795,g48ccf36440+885b902d19,g4b2f1765b6+0c565e8f25,g5320a0a9f6+bd4bf1dc76,g56364267ca+403c24672b,g56b687f8c9+585f0f68a2,g5c4744a4d9+78cd207961,g5ffd174ac0+bd4bf1dc76,g6075d09f38+3075de592a,g667d525e37+cacede5508,g6f3e93b5a3+da81c812ee,g71f27ac40c+cacede5508,g7212e027e3+eb621d73aa,g774830318a+18d2b9fa6c,g7985c39107+62d12e78cb,g79ca90bc5c+fa2cc03294,g881bdbfe6c+cacede5508,g91fc1fa0cf+82a115f028,g961520b1fb+2534687f64,g96f01af41f+f2060f23b6,g9ca82378b8+cacede5508,g9d27549199+78cd207961,gb065e2a02a+ad48cbcda4,gb1df4690d6+585f0f68a2,gb35d6563ee+62d12e78cb,gbc3249ced9+bd4bf1dc76,gbec6a3398f+bd4bf1dc76,gd01420fc67+bd4bf1dc76,gd59336e7c4+c7bb92e648,gf46e8334de+81c9a61069,gfed783d017+bd4bf1dc76,v25.0.1.rc3
LSST Data Management Base Package
Loading...
Searching...
No Matches
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
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77

◆ 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
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:51

◆ 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(psf1.getAveragePosition()).getDeterminantRadius()
290 sig2 = psf2.computeShape(psf2.getAveragePosition()).getDeterminantRadius()
291 sig = max(sig1, sig2)
292 psfBBox1 = psf1.computeBBox(psf1.getAveragePosition())
293 psfBBox2 = psf2.computeBBox(psf2.getAveragePosition())
294 return max(10 * sig, psfBBox1.getWidth(), psfBBox1.getHeight(),
295 psfBBox2.getWidth(), psfBBox2.getHeight())
296
int max
A polymorphic base class for representing an image's Point Spread Function.
Definition: Psf.h:76

◆ 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
An integer coordinate rectangle.
Definition: Box.h:55

◆ 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 -------
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
A Psf defined by a Kernel.
Definition: KernelPsf.h:36

◆ 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 -------
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
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Definition: CoaddPsf.h:58

◆ 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.
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

◆ 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:83

◆ 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

◆ 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:361

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: