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 | Public Attributes | Static Public Attributes | List of all members
lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask Class Reference
Inheritance diagram for lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask:

Public Member Functions

def __init__ (self, *args, **kwargs)
 
def run (self, expRefList, expDatasetType, imageScalerList=None, refExpDataRef=None, refImageScaler=None)
 
def selectRefExposure (self, expRefList, imageScalerList, expDatasetType)
 
def matchBackgrounds (self, refExposure, sciExposure)
 

Public Attributes

 sctrl
 
 debugDataIdString
 

Static Public Attributes

 ConfigClass = MatchBackgroundsConfig
 

Detailed Description

Definition at line 139 of file matchBackgrounds.py.

Constructor & Destructor Documentation

◆ __init__()

def lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.__init__ (   self,
args,
**  kwargs 
)

Definition at line 143 of file matchBackgrounds.py.

143  def __init__(self, *args, **kwargs):
144  pipeBase.Task.__init__(self, *args, **kwargs)
145 
146  self.sctrl = afwMath.StatisticsControl()
147  self.sctrl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes))
148  self.sctrl.setNanSafe(True)
149 
Pass parameters to a Statistics object.
Definition: Statistics.h:92

Member Function Documentation

◆ matchBackgrounds()

def lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.matchBackgrounds (   self,
  refExposure,
  sciExposure 
)
Match science exposure's background level to that of reference exposure.

Process creates a difference image of the reference exposure minus the science exposure, and then
generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
already has detections set. If detections have not been set/masked, sources will bias the
background estimation.
The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
or by polynomial interpolation by the Approximate class. This model of difference image
is added to the science exposure in memory.
Fit diagnostics are also calculated and returned.

@param[in] refExposure: reference exposure
@param[in,out] sciExposure: science exposure; modified by changing the background level
    to match that of the reference exposure
@returns a pipBase.Struct with fields:
    - backgroundModel: an afw.math.Approximate or an afw.math.Background.
    - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
    - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
      should be comparable to difference image's mean variance.
    - diffImVar: the mean variance of the difference image.

Definition at line 332 of file matchBackgrounds.py.

332  def matchBackgrounds(self, refExposure, sciExposure):
333  """
334  Match science exposure's background level to that of reference exposure.
335 
336  Process creates a difference image of the reference exposure minus the science exposure, and then
337  generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
338  already has detections set. If detections have not been set/masked, sources will bias the
339  background estimation.
340  The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
341  or by polynomial interpolation by the Approximate class. This model of difference image
342  is added to the science exposure in memory.
343  Fit diagnostics are also calculated and returned.
344 
345  @param[in] refExposure: reference exposure
346  @param[in,out] sciExposure: science exposure; modified by changing the background level
347  to match that of the reference exposure
348  @returns a pipBase.Struct with fields:
349  - backgroundModel: an afw.math.Approximate or an afw.math.Background.
350  - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
351  - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
352  should be comparable to difference image's mean variance.
353  - diffImVar: the mean variance of the difference image.
354  """
355 
356  if lsstDebug.Info(__name__).savefits:
357  refExposure.writeFits(lsstDebug.Info(__name__).figpath + 'refExposure.fits')
358  sciExposure.writeFits(lsstDebug.Info(__name__).figpath + 'sciExposure.fits')
359 
360  # Check Configs for polynomials:
361  if self.config.usePolynomial:
362  x, y = sciExposure.getDimensions()
363  shortSideLength = min(x, y)
364  if shortSideLength < self.config.binSize:
365  raise ValueError("%d = config.binSize > shorter dimension = %d" % (self.config.binSize,
366  shortSideLength))
367  npoints = shortSideLength // self.config.binSize
368  if shortSideLength % self.config.binSize != 0:
369  npoints += 1
370 
371  if self.config.order > npoints - 1:
372  raise ValueError("%d = config.order > npoints - 1 = %d" % (self.config.order, npoints - 1))
373 
374  # Check that exposures are same shape
375  if (sciExposure.getDimensions() != refExposure.getDimensions()):
376  wSci, hSci = sciExposure.getDimensions()
377  wRef, hRef = refExposure.getDimensions()
378  raise RuntimeError(
379  "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)" %
380  (wSci, hSci, wRef, hRef))
381 
382  statsFlag = getattr(afwMath, self.config.gridStatistic)
383  self.sctrl.setNumSigmaClip(self.config.numSigmaClip)
384  self.sctrl.setNumIter(self.config.numIter)
385 
386  im = refExposure.getMaskedImage()
387  diffMI = im.Factory(im, True)
388  diffMI -= sciExposure.getMaskedImage()
389 
390  width = diffMI.getWidth()
391  height = diffMI.getHeight()
392  nx = width // self.config.binSize
393  if width % self.config.binSize != 0:
394  nx += 1
395  ny = height // self.config.binSize
396  if height % self.config.binSize != 0:
397  ny += 1
398 
399  bctrl = afwMath.BackgroundControl(nx, ny, self.sctrl, statsFlag)
400  bctrl.setUndersampleStyle(self.config.undersampleStyle)
401 
402  bkgd = afwMath.makeBackground(diffMI, bctrl)
403 
404  # Some config and input checks if config.usePolynomial:
405  # 1) Check that order/bin size make sense:
406  # 2) Change binsize or order if underconstrained.
407  if self.config.usePolynomial:
408  order = self.config.order
409  bgX, bgY, bgZ, bgdZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
410  minNumberGridPoints = min(len(set(bgX)), len(set(bgY)))
411  if len(bgZ) == 0:
412  raise ValueError("No overlap with reference. Nothing to match")
413  elif minNumberGridPoints <= self.config.order:
414  # must either lower order or raise number of bins or throw exception
415  if self.config.undersampleStyle == "THROW_EXCEPTION":
416  raise ValueError("Image does not cover enough of ref image for order and binsize")
417  elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
418  self.log.warning("Reducing order to %d", (minNumberGridPoints - 1))
419  order = minNumberGridPoints - 1
420  elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
421  newBinSize = (minNumberGridPoints*self.config.binSize) // (self.config.order + 1)
422  bctrl.setNxSample(newBinSize)
423  bctrl.setNySample(newBinSize)
424  bkgd = afwMath.makeBackground(diffMI, bctrl) # do over
425  self.log.warning("Decreasing binsize to %d", newBinSize)
426 
427  # If there is no variance in any image pixels, do not weight bins by inverse variance
428  isUniformImageDiff = not numpy.any(bgdZ > self.config.gridStdevEpsilon)
429  weightByInverseVariance = False if isUniformImageDiff else self.config.approxWeighting
430 
431  # Add offset to sciExposure
432  try:
433  if self.config.usePolynomial:
434  actrl = afwMath.ApproximateControl(afwMath.ApproximateControl.CHEBYSHEV,
435  order, order, weightByInverseVariance)
436  undersampleStyle = getattr(afwMath, self.config.undersampleStyle)
437  approx = bkgd.getApproximate(actrl, undersampleStyle)
438  bkgdImage = approx.getImage()
439  else:
440  bkgdImage = bkgd.getImageF(self.config.interpStyle, self.config.undersampleStyle)
441  except Exception as e:
442  raise RuntimeError("Background/Approximation failed to interp image %s: %s" % (
443  self.debugDataIdString, e))
444 
445  sciMI = sciExposure.getMaskedImage()
446  sciMI += bkgdImage
447  del sciMI
448 
449  # Need RMS from fit: 2895 will replace this:
450  rms = 0.0
451  X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
452  x0, y0 = diffMI.getXY0()
453  modelValueArr = numpy.empty(len(Z))
454  for i in range(len(X)):
455  modelValueArr[i] = bkgdImage[int(X[i]-x0), int(Y[i]-y0), afwImage.LOCAL]
456  resids = Z - modelValueArr
457  rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))
458 
459  if lsstDebug.Info(__name__).savefits:
460  sciExposure.writeFits(lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits')
461 
462  if lsstDebug.Info(__name__).savefig:
463  bbox = geom.Box2D(refExposure.getMaskedImage().getBBox())
464  try:
465  self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr, resids)
466  except Exception as e:
467  self.log.warning('Debug plot not generated: %s', e)
468 
469  meanVar = afwMath.makeStatistics(diffMI.getVariance(), diffMI.getMask(),
470  afwMath.MEANCLIP, self.sctrl).getValue()
471 
472  diffIm = diffMI.getImage()
473  diffIm -= bkgdImage # diffMI should now have a mean ~ 0
474  del diffIm
475  mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE, self.sctrl).getValue()
476 
477  outBkgd = approx if self.config.usePolynomial else bkgd
478 
479  return pipeBase.Struct(
480  backgroundModel=outBkgd,
481  fitRMS=rms,
482  matchedMSE=mse,
483  diffImVar=meanVar)
484 
int min
Control how to make an approximation.
Definition: Approximate.h:48
Pass parameters to a Background object.
Definition: Background.h:56
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
daf::base::PropertySet * set
Definition: fits.cc:912
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
std::shared_ptr< Background > makeBackground(ImageT const &img, BackgroundControl const &bgCtrl)
A convenience function that uses function overloading to make the correct type of Background.
Definition: Background.h:526

◆ run()

def lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.run (   self,
  expRefList,
  expDatasetType,
  imageScalerList = None,
  refExpDataRef = None,
  refImageScaler = None 
)
Match the backgrounds of a list of coadd temp exposures to a reference coadd temp exposure.

Choose a refExpDataRef automatically if none supplied.

@param[in] expRefList: list of data references to science exposures to be background-matched;
    all exposures must exist.
@param[in] expDatasetType: dataset type of exposures, e.g. 'goodSeeingCoadd_tempExp'
@param[in] imageScalerList: list of image scalers (coaddUtils.ImageScaler);
    if None then the images are not scaled
@param[in] refExpDataRef: data reference for the reference exposure.
    If None, then this task selects the best exposures from expRefList.
    if not None then must be one of the exposures in expRefList.
@param[in] refImageScaler: image scaler for reference image;
    ignored if refExpDataRef is None, else scaling is not performed if None

@return: a pipBase.Struct containing these fields:
- backgroundInfoList: a list of pipeBase.Struct, one per exposure in expRefList,
    each of which contains these fields:
    - isReference: this is the reference exposure (only one returned Struct will
        contain True for this value, unless the ref exposure is listed multiple times)
    - backgroundModel: differential background model (afw.Math.Background or afw.Math.Approximate).
        Add this to the science exposure to match the reference exposure.
    - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
    - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
      should be comparable to difference image's mean variance.
    - diffImVar: the mean variance of the difference image.
    All fields except isReference will be None if isReference True or the fit failed.

@warning: all exposures must exist on disk

Definition at line 151 of file matchBackgrounds.py.

151  def run(self, expRefList, expDatasetType, imageScalerList=None, refExpDataRef=None, refImageScaler=None):
152  """Match the backgrounds of a list of coadd temp exposures to a reference coadd temp exposure.
153 
154  Choose a refExpDataRef automatically if none supplied.
155 
156  @param[in] expRefList: list of data references to science exposures to be background-matched;
157  all exposures must exist.
158  @param[in] expDatasetType: dataset type of exposures, e.g. 'goodSeeingCoadd_tempExp'
159  @param[in] imageScalerList: list of image scalers (coaddUtils.ImageScaler);
160  if None then the images are not scaled
161  @param[in] refExpDataRef: data reference for the reference exposure.
162  If None, then this task selects the best exposures from expRefList.
163  if not None then must be one of the exposures in expRefList.
164  @param[in] refImageScaler: image scaler for reference image;
165  ignored if refExpDataRef is None, else scaling is not performed if None
166 
167  @return: a pipBase.Struct containing these fields:
168  - backgroundInfoList: a list of pipeBase.Struct, one per exposure in expRefList,
169  each of which contains these fields:
170  - isReference: this is the reference exposure (only one returned Struct will
171  contain True for this value, unless the ref exposure is listed multiple times)
172  - backgroundModel: differential background model (afw.Math.Background or afw.Math.Approximate).
173  Add this to the science exposure to match the reference exposure.
174  - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
175  - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
176  should be comparable to difference image's mean variance.
177  - diffImVar: the mean variance of the difference image.
178  All fields except isReference will be None if isReference True or the fit failed.
179 
180  @warning: all exposures must exist on disk
181  """
182 
183  numExp = len(expRefList)
184  if numExp < 1:
185  raise pipeBase.TaskError("No exposures to match")
186 
187  if expDatasetType is None:
188  raise pipeBase.TaskError("Must specify expDatasetType")
189 
190  if imageScalerList is None:
191  self.log.info("imageScalerList is None; no scaling will be performed")
192  imageScalerList = [None] * numExp
193 
194  if len(expRefList) != len(imageScalerList):
195  raise RuntimeError("len(expRefList) = %s != %s = len(imageScalerList)" %
196  (len(expRefList), len(imageScalerList)))
197 
198  refInd = None
199  if refExpDataRef is None:
200  # select the best reference exposure from expRefList
201  refInd = self.selectRefExposure(
202  expRefList=expRefList,
203  imageScalerList=imageScalerList,
204  expDatasetType=expDatasetType,
205  )
206  refExpDataRef = expRefList[refInd]
207  refImageScaler = imageScalerList[refInd]
208 
209  # refIndSet is the index of all exposures in expDataList that match the reference.
210  # It is used to avoid background-matching an exposure to itself. It is a list
211  # because it is possible (though unlikely) that expDataList will contain duplicates.
212  expKeyList = refExpDataRef.butlerSubset.butler.getKeys(expDatasetType)
213  refMatcher = DataRefMatcher(refExpDataRef.butlerSubset.butler, expDatasetType)
214  refIndSet = set(refMatcher.matchList(ref0=refExpDataRef, refList=expRefList))
215 
216  if refInd is not None and refInd not in refIndSet:
217  raise RuntimeError("Internal error: selected reference %s not found in expRefList")
218 
219  refExposure = refExpDataRef.get(expDatasetType, immediate=True)
220  if refImageScaler is not None:
221  refMI = refExposure.getMaskedImage()
222  refImageScaler.scaleMaskedImage(refMI)
223 
224  debugIdKeyList = tuple(set(expKeyList) - set(['tract', 'patch']))
225 
226  self.log.info("Matching %d Exposures", numExp)
227 
228  backgroundInfoList = []
229  for ind, (toMatchRef, imageScaler) in enumerate(zip(expRefList, imageScalerList)):
230  if ind in refIndSet:
231  backgroundInfoStruct = pipeBase.Struct(
232  isReference=True,
233  backgroundModel=None,
234  fitRMS=0.0,
235  matchedMSE=None,
236  diffImVar=None,
237  )
238  else:
239  self.log.info("Matching background of %s to %s", toMatchRef.dataId, refExpDataRef.dataId)
240  try:
241  toMatchExposure = toMatchRef.get(expDatasetType, immediate=True)
242  if imageScaler is not None:
243  toMatchMI = toMatchExposure.getMaskedImage()
244  imageScaler.scaleMaskedImage(toMatchMI)
245  # store a string specifying the visit to label debug plot
246  self.debugDataIdString = ''.join([str(toMatchRef.dataId[vk]) for vk in debugIdKeyList])
247  backgroundInfoStruct = self.matchBackgrounds(
248  refExposure=refExposure,
249  sciExposure=toMatchExposure,
250  )
251  backgroundInfoStruct.isReference = False
252  except Exception as e:
253  self.log.warning("Failed to fit background %s: %s", toMatchRef.dataId, e)
254  backgroundInfoStruct = pipeBase.Struct(
255  isReference=False,
256  backgroundModel=None,
257  fitRMS=None,
258  matchedMSE=None,
259  diffImVar=None,
260  )
261 
262  backgroundInfoList.append(backgroundInfoStruct)
263 
264  return pipeBase.Struct(
265  backgroundInfoList=backgroundInfoList)
266 
def run(self, coaddExposures, bbox, wcs)
Definition: getTemplate.py:603

◆ selectRefExposure()

def lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.selectRefExposure (   self,
  expRefList,
  imageScalerList,
  expDatasetType 
)
Find best exposure to use as the reference exposure

Calculate an appropriate reference exposure by minimizing a cost function that penalizes
high variance,  high background level, and low coverage. Use the following config parameters:
- bestRefWeightCoverage
- bestRefWeightVariance
- bestRefWeightLevel

@param[in] expRefList: list of data references to exposures.
    Retrieves dataset type specified by expDatasetType.
    If an exposure is not found, it is skipped with a warning.
@param[in] imageScalerList: list of image scalers (coaddUtils.ImageScaler);
    must be the same length as expRefList
@param[in] expDatasetType: dataset type of exposure: e.g. 'goodSeeingCoadd_tempExp'

@return: index of best exposure

@raise pipeBase.TaskError if none of the exposures in expRefList are found.

Definition at line 268 of file matchBackgrounds.py.

268  def selectRefExposure(self, expRefList, imageScalerList, expDatasetType):
269  """Find best exposure to use as the reference exposure
270 
271  Calculate an appropriate reference exposure by minimizing a cost function that penalizes
272  high variance, high background level, and low coverage. Use the following config parameters:
273  - bestRefWeightCoverage
274  - bestRefWeightVariance
275  - bestRefWeightLevel
276 
277  @param[in] expRefList: list of data references to exposures.
278  Retrieves dataset type specified by expDatasetType.
279  If an exposure is not found, it is skipped with a warning.
280  @param[in] imageScalerList: list of image scalers (coaddUtils.ImageScaler);
281  must be the same length as expRefList
282  @param[in] expDatasetType: dataset type of exposure: e.g. 'goodSeeingCoadd_tempExp'
283 
284  @return: index of best exposure
285 
286  @raise pipeBase.TaskError if none of the exposures in expRefList are found.
287  """
288  self.log.info("Calculating best reference visit")
289  varList = []
290  meanBkgdLevelList = []
291  coverageList = []
292 
293  if len(expRefList) != len(imageScalerList):
294  raise RuntimeError("len(expRefList) = %s != %s = len(imageScalerList)" %
295  (len(expRefList), len(imageScalerList)))
296 
297  for expRef, imageScaler in zip(expRefList, imageScalerList):
298  exposure = expRef.get(expDatasetType, immediate=True)
299  maskedImage = exposure.getMaskedImage()
300  if imageScaler is not None:
301  try:
302  imageScaler.scaleMaskedImage(maskedImage)
303  except Exception:
304  # need to put a place holder in Arr
305  varList.append(numpy.nan)
306  meanBkgdLevelList.append(numpy.nan)
307  coverageList.append(numpy.nan)
308  continue
309  statObjIm = afwMath.makeStatistics(maskedImage.getImage(), maskedImage.getMask(),
310  afwMath.MEAN | afwMath.NPOINT | afwMath.VARIANCE, self.sctrl)
311  meanVar, meanVarErr = statObjIm.getResult(afwMath.VARIANCE)
312  meanBkgdLevel, meanBkgdLevelErr = statObjIm.getResult(afwMath.MEAN)
313  npoints, npointsErr = statObjIm.getResult(afwMath.NPOINT)
314  varList.append(meanVar)
315  meanBkgdLevelList.append(meanBkgdLevel)
316  coverageList.append(npoints)
317  if not coverageList:
318  raise pipeBase.TaskError(
319  "None of the candidate %s exist; cannot select best reference exposure" % (expDatasetType,))
320 
321  # Normalize metrics to range from 0 to 1
322  varArr = numpy.array(varList)/numpy.nanmax(varList)
323  meanBkgdLevelArr = numpy.array(meanBkgdLevelList)/numpy.nanmax(meanBkgdLevelList)
324  coverageArr = numpy.nanmin(coverageList)/numpy.array(coverageList)
325 
326  costFunctionArr = self.config.bestRefWeightVariance * varArr
327  costFunctionArr += self.config.bestRefWeightLevel * meanBkgdLevelArr
328  costFunctionArr += self.config.bestRefWeightCoverage * coverageArr
329  return numpy.nanargmin(costFunctionArr)
330 

Member Data Documentation

◆ ConfigClass

lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.ConfigClass = MatchBackgroundsConfig
static

Definition at line 140 of file matchBackgrounds.py.

◆ debugDataIdString

lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.debugDataIdString

Definition at line 246 of file matchBackgrounds.py.

◆ sctrl

lsst.pipe.tasks.matchBackgrounds.MatchBackgroundsTask.sctrl

Definition at line 146 of file matchBackgrounds.py.


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