LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
matchBackgrounds.py
Go to the documentation of this file.
1 from builtins import zip
2 from builtins import range
3 from builtins import object
4 #
5 # LSST Data Management System
6 # Copyright 2008, 2009, 2010 LSST Corporation.
7 #
8 # This product includes software developed by the
9 # LSST Project (http://www.lsst.org/).
10 #
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILIY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the LSST License Statement and
22 # the GNU General Public License along with this program. If not,
23 # see <http://www.lsstcorp.org/LegalNotices/>.
24 
25 import numpy
26 import lsst.afw.image as afwImage
27 import lsst.afw.math as afwMath
28 import lsst.afw.geom as afwGeom
29 import lsst.pex.config as pexConfig
30 import lsst.pipe.base as pipeBase
31 import lsstDebug
32 
33 
34 class MatchBackgroundsConfig(pexConfig.Config):
35 
36  usePolynomial = pexConfig.Field(
37  dtype=bool,
38  doc="Fit background difference with Chebychev polynomial interpolation "
39  "(using afw.math.Approximate)? If False, fit with spline interpolation using afw.math.Background",
40  default=False
41  )
42  order = pexConfig.Field(
43  dtype=int,
44  doc="Order of Chebyshev polynomial background model. Ignored if usePolynomial False",
45  default=8
46  )
47  badMaskPlanes = pexConfig.ListField(
48  doc="Names of mask planes to ignore while estimating the background",
49  dtype=str, default=["NO_DATA", "DETECTED", "DETECTED_NEGATIVE", "SAT", "BAD", "INTRP", "CR"],
50  itemCheck=lambda x: x in afwImage.MaskU().getMaskPlaneDict(),
51  )
52  gridStatistic = pexConfig.ChoiceField(
53  dtype=str,
54  doc="Type of statistic to estimate pixel value for the grid points",
55  default="MEAN",
56  allowed={
57  "MEAN": "mean",
58  "MEDIAN": "median",
59  "MEANCLIP": "clipped mean"
60  }
61  )
62  undersampleStyle = pexConfig.ChoiceField(
63  doc="Behaviour if there are too few points in grid for requested interpolation style. "
64  "Note: INCREASE_NXNYSAMPLE only allowed for usePolynomial=True.",
65  dtype=str,
66  default="REDUCE_INTERP_ORDER",
67  allowed={
68  "THROW_EXCEPTION": "throw an exception if there are too few points",
69  "REDUCE_INTERP_ORDER": "use an interpolation style with a lower order.",
70  "INCREASE_NXNYSAMPLE": "Increase the number of samples used to make the interpolation grid.",
71  }
72  )
73  binSize = pexConfig.Field(
74  doc="Bin size for gridding the difference image and fitting a spatial model",
75  dtype=int,
76  default=256
77  )
78  interpStyle = pexConfig.ChoiceField(
79  dtype=str,
80  doc="Algorithm to interpolate the background values; ignored if usePolynomial is True"
81  "Maps to an enum; see afw.math.Background",
82  default="AKIMA_SPLINE",
83  allowed={
84  "CONSTANT": "Use a single constant value",
85  "LINEAR": "Use linear interpolation",
86  "NATURAL_SPLINE": "cubic spline with zero second derivative at endpoints",
87  "AKIMA_SPLINE": "higher-level nonlinear spline that is more robust to outliers",
88  "NONE": "No background estimation is to be attempted",
89  }
90  )
91  numSigmaClip = pexConfig.Field(
92  dtype=int,
93  doc="Sigma for outlier rejection; ignored if gridStatistic != 'MEANCLIP'.",
94  default=3
95  )
96  numIter = pexConfig.Field(
97  dtype=int,
98  doc="Number of iterations of outlier rejection; ignored if gridStatistic != 'MEANCLIP'.",
99  default=2
100  )
101  bestRefWeightCoverage = pexConfig.RangeField(
102  dtype=float,
103  doc="Weight given to coverage (number of pixels that overlap with patch), "
104  "when calculating best reference exposure. Higher weight prefers exposures with high coverage."
105  "Ignored when reference visit is supplied",
106  default=0.4,
107  min=0., max=1.
108  )
109  bestRefWeightVariance = pexConfig.RangeField(
110  dtype=float,
111  doc="Weight given to image variance when calculating best reference exposure. "
112  "Higher weight prefers exposures with low image variance. Ignored when reference visit is supplied",
113  default=0.4,
114  min=0., max=1.
115  )
116  bestRefWeightLevel = pexConfig.RangeField(
117  dtype=float,
118  doc="Weight given to mean background level when calculating best reference exposure. "
119  "Higher weight prefers exposures with low mean background level. "
120  "Ignored when reference visit is supplied.",
121  default=0.2,
122  min=0., max=1.
123  )
124  approxWeighting = pexConfig.Field(
125  dtype=bool,
126  doc=("Use inverse-variance weighting when approximating background offset model? " +
127  "This will fail when the background offset is constant " +
128  "(this is usually only the case in testing with artificial images)." +
129  "(usePolynomial=True)"),
130  default=True,
131  )
132  gridStdevEpsilon = pexConfig.RangeField(
133  dtype=float,
134  doc="Tolerance on almost zero standard deviation in a background-offset grid bin. "
135  "If all bins have a standard deviation below this value, the background offset model "
136  "is approximated without inverse-variance weighting. (usePolynomial=True)",
137  default=1e-8,
138  min=0.
139  )
140 
141 
142 class MatchBackgroundsTask(pipeBase.Task):
143  ConfigClass = MatchBackgroundsConfig
144  _DefaultName = "matchBackgrounds"
145 
146  def __init__(self, *args, **kwargs):
147  pipeBase.Task.__init__(self, *args, **kwargs)
148 
150  self.sctrl.setAndMask(afwImage.MaskU.getPlaneBitMask(self.config.badMaskPlanes))
151  self.sctrl.setNanSafe(True)
152 
153  @pipeBase.timeMethod
154  def run(self, expRefList, expDatasetType, imageScalerList=None, refExpDataRef=None, refImageScaler=None):
155  """Match the backgrounds of a list of coadd temp exposures to a reference coadd temp exposure.
156 
157  Choose a refExpDataRef automatically if none supplied.
158 
159  @param[in] expRefList: list of data references to science exposures to be background-matched;
160  all exposures must exist.
161  @param[in] expDatasetType: dataset type of exposures, e.g. 'goodSeeingCoadd_tempExp'
162  @param[in] imageScalerList: list of image scalers (coaddUtils.ImageScaler);
163  if None then the images are not scaled
164  @param[in] refExpDataRef: data reference for the reference exposure.
165  If None, then this task selects the best exposures from expRefList.
166  if not None then must be one of the exposures in expRefList.
167  @param[in] refImageScaler: image scaler for reference image;
168  ignored if refExpDataRef is None, else scaling is not performed if None
169 
170  @return: a pipBase.Struct containing these fields:
171  - backgroundInfoList: a list of pipeBase.Struct, one per exposure in expRefList,
172  each of which contains these fields:
173  - isReference: this is the reference exposure (only one returned Struct will
174  contain True for this value, unless the ref exposure is listed multiple times)
175  - backgroundModel: differential background model (afw.Math.Background or afw.Math.Approximate).
176  Add this to the science exposure to match the reference exposure.
177  - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
178  - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
179  should be comparable to difference image's mean variance.
180  - diffImVar: the mean variance of the difference image.
181  All fields except isReference will be None if isReference True or the fit failed.
182 
183  @warning: all exposures must exist on disk
184  """
185 
186  numExp = len(expRefList)
187  if numExp < 1:
188  raise pipeBase.TaskError("No exposures to match")
189 
190  if expDatasetType is None:
191  raise pipeBase.TaskError("Must specify expDatasetType")
192 
193  if imageScalerList is None:
194  self.log.info("imageScalerList is None; no scaling will be performed")
195  imageScalerList = [None] * numExp
196 
197  if len(expRefList) != len(imageScalerList):
198  raise RuntimeError("len(expRefList) = %s != %s = len(imageScalerList)" %
199  (len(expRefList), len(imageScalerList)))
200 
201  refInd = None
202  if refExpDataRef is None:
203  # select the best reference exposure from expRefList
204  refInd = self.selectRefExposure(
205  expRefList=expRefList,
206  imageScalerList=imageScalerList,
207  expDatasetType=expDatasetType,
208  )
209  refExpDataRef = expRefList[refInd]
210  refImageScaler = imageScalerList[refInd]
211 
212  # refIndSet is the index of all exposures in expDataList that match the reference.
213  # It is used to avoid background-matching an exposure to itself. It is a list
214  # because it is possible (though unlikely) that expDataList will contain duplicates.
215  expKeyList = refExpDataRef.butlerSubset.butler.getKeys(expDatasetType)
216  refMatcher = DataRefMatcher(refExpDataRef.butlerSubset.butler, expDatasetType)
217  refIndSet = set(refMatcher.matchList(ref0=refExpDataRef, refList=expRefList))
218 
219  if refInd is not None and refInd not in refIndSet:
220  raise RuntimeError("Internal error: selected reference %s not found in expRefList")
221 
222  refExposure = refExpDataRef.get(expDatasetType, immediate=True)
223  if refImageScaler is not None:
224  refMI = refExposure.getMaskedImage()
225  refImageScaler.scaleMaskedImage(refMI)
226 
227  debugIdKeyList = tuple(set(expKeyList) - set(['tract', 'patch']))
228 
229  self.log.info("Matching %d Exposures" % (numExp))
230 
231  backgroundInfoList = []
232  for ind, (toMatchRef, imageScaler) in enumerate(zip(expRefList, imageScalerList)):
233  if ind in refIndSet:
234  backgroundInfoStruct = pipeBase.Struct(
235  isReference=True,
236  backgroundModel=None,
237  fitRMS=0.0,
238  matchedMSE=None,
239  diffImVar=None,
240  )
241  else:
242  self.log.info("Matching background of %s to %s" % (toMatchRef.dataId, refExpDataRef.dataId))
243  try:
244  toMatchExposure = toMatchRef.get(expDatasetType, immediate=True)
245  if imageScaler is not None:
246  toMatchMI = toMatchExposure.getMaskedImage()
247  imageScaler.scaleMaskedImage(toMatchMI)
248  # store a string specifying the visit to label debug plot
249  self.debugDataIdString = ''.join([str(toMatchRef.dataId[vk]) for vk in debugIdKeyList])
250  backgroundInfoStruct = self.matchBackgrounds(
251  refExposure=refExposure,
252  sciExposure=toMatchExposure,
253  )
254  backgroundInfoStruct.isReference = False
255  except Exception as e:
256  self.log.warn("Failed to fit background %s: %s" % (toMatchRef.dataId, e))
257  backgroundInfoStruct = pipeBase.Struct(
258  isReference=False,
259  backgroundModel=None,
260  fitRMS=None,
261  matchedMSE=None,
262  diffImVar=None,
263  )
264 
265  backgroundInfoList.append(backgroundInfoStruct)
266 
267  return pipeBase.Struct(
268  backgroundInfoList=backgroundInfoList)
269 
270  @pipeBase.timeMethod
271  def selectRefExposure(self, expRefList, imageScalerList, expDatasetType):
272  """Find best exposure to use as the reference exposure
273 
274  Calculate an appropriate reference exposure by minimizing a cost function that penalizes
275  high variance, high background level, and low coverage. Use the following config parameters:
276  - bestRefWeightCoverage
277  - bestRefWeightVariance
278  - bestRefWeightLevel
279 
280  @param[in] expRefList: list of data references to exposures.
281  Retrieves dataset type specified by expDatasetType.
282  If an exposure is not found, it is skipped with a warning.
283  @param[in] imageScalerList: list of image scalers (coaddUtils.ImageScaler);
284  must be the same length as expRefList
285  @param[in] expDatasetType: dataset type of exposure: e.g. 'goodSeeingCoadd_tempExp'
286 
287  @return: index of best exposure
288 
289  @raise pipeBase.TaskError if none of the exposures in expRefList are found.
290  """
291  self.log.info("Calculating best reference visit")
292  varList = []
293  meanBkgdLevelList = []
294  coverageList = []
295 
296  if len(expRefList) != len(imageScalerList):
297  raise RuntimeError("len(expRefList) = %s != %s = len(imageScalerList)" %
298  (len(expRefList), len(imageScalerList)))
299 
300  for expRef, imageScaler in zip(expRefList, imageScalerList):
301  exposure = expRef.get(expDatasetType, immediate=True)
302  maskedImage = exposure.getMaskedImage()
303  if imageScaler is not None:
304  try:
305  imageScaler.scaleMaskedImage(maskedImage)
306  except:
307  # need to put a place holder in Arr
308  varList.append(numpy.nan)
309  meanBkgdLevelList.append(numpy.nan)
310  coverageList.append(numpy.nan)
311  continue
312  statObjIm = afwMath.makeStatistics(maskedImage.getImage(), maskedImage.getMask(),
313  afwMath.MEAN | afwMath.NPOINT | afwMath.VARIANCE, self.sctrl)
314  meanVar, meanVarErr = statObjIm.getResult(afwMath.VARIANCE)
315  meanBkgdLevel, meanBkgdLevelErr = statObjIm.getResult(afwMath.MEAN)
316  npoints, npointsErr = statObjIm.getResult(afwMath.NPOINT)
317  varList.append(meanVar)
318  meanBkgdLevelList.append(meanBkgdLevel)
319  coverageList.append(npoints)
320  if not coverageList:
321  raise pipeBase.TaskError(
322  "None of the candidate %s exist; cannot select best reference exposure" % (expDatasetType,))
323 
324  # Normalize metrics to range from 0 to 1
325  varArr = numpy.array(varList)/numpy.nanmax(varList)
326  meanBkgdLevelArr = numpy.array(meanBkgdLevelList)/numpy.nanmax(meanBkgdLevelList)
327  coverageArr = numpy.nanmin(coverageList)/numpy.array(coverageList)
328 
329  costFunctionArr = self.config.bestRefWeightVariance * varArr
330  costFunctionArr += self.config.bestRefWeightLevel * meanBkgdLevelArr
331  costFunctionArr += self.config.bestRefWeightCoverage * coverageArr
332  return numpy.nanargmin(costFunctionArr)
333 
334  @pipeBase.timeMethod
335  def matchBackgrounds(self, refExposure, sciExposure):
336  """
337  Match science exposure's background level to that of reference exposure.
338 
339  Process creates a difference image of the reference exposure minus the science exposure, and then
340  generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
341  already has detections set. If detections have not been set/masked, sources will bias the
342  background estimation.
343  The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
344  or by polynomial interpolation by the Approximate class. This model of difference image is added to the
345  science exposure in memory.
346  Fit diagnostics are also calculated and returned.
347 
348  @param[in] refExposure: reference exposure
349  @param[in,out] sciExposure: science exposure; modified by changing the background level
350  to match that of the reference exposure
351  @returns a pipBase.Struct with fields:
352  - backgroundModel: an afw.math.Approximate or an afw.math.Background.
353  - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
354  - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
355  should be comparable to difference image's mean variance.
356  - diffImVar: the mean variance of the difference image.
357  """
358 
359  if lsstDebug.Info(__name__).savefits:
360  refExposure.writeFits(lsstDebug.Info(__name__).figpath + 'refExposure.fits')
361  sciExposure.writeFits(lsstDebug.Info(__name__).figpath + 'sciExposure.fits')
362 
363  # Check Configs for polynomials:
364  if self.config.usePolynomial:
365  x, y = sciExposure.getDimensions()
366  shortSideLength = min(x, y)
367  if shortSideLength < self.config.binSize:
368  raise ValueError("%d = config.binSize > shorter dimension = %d" % (self.config.binSize,
369  shortSideLength))
370  npoints = shortSideLength // self.config.binSize
371  if shortSideLength % self.config.binSize != 0:
372  npoints += 1
373 
374  if self.config.order > npoints - 1:
375  raise ValueError("%d = config.order > npoints - 1 = %d" % (self.config.order, npoints - 1))
376 
377  # Check that exposures are same shape
378  if (sciExposure.getDimensions() != refExposure.getDimensions()):
379  wSci, hSci = sciExposure.getDimensions()
380  wRef, hRef = refExposure.getDimensions()
381  raise RuntimeError(
382  "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)" %
383  (wSci, hSci, wRef, hRef))
384 
385  statsFlag = getattr(afwMath, self.config.gridStatistic)
386  self.sctrl.setNumSigmaClip(self.config.numSigmaClip)
387  self.sctrl.setNumIter(self.config.numIter)
388 
389  im = refExposure.getMaskedImage()
390  diffMI = im.Factory(im, True)
391  diffMI -= sciExposure.getMaskedImage()
392 
393  width = diffMI.getWidth()
394  height = diffMI.getHeight()
395  nx = width // self.config.binSize
396  if width % self.config.binSize != 0:
397  nx += 1
398  ny = height // self.config.binSize
399  if height % self.config.binSize != 0:
400  ny += 1
401 
402  bctrl = afwMath.BackgroundControl(nx, ny, self.sctrl, statsFlag)
403  bctrl.setUndersampleStyle(self.config.undersampleStyle)
404  bctrl.setInterpStyle(self.config.interpStyle)
405 
406  bkgd = afwMath.makeBackground(diffMI, bctrl)
407 
408  # Some config and input checks if config.usePolynomial:
409  # 1) Check that order/bin size make sense:
410  # 2) Change binsize or order if underconstrained.
411  if self.config.usePolynomial:
412  order = self.config.order
413  bgX, bgY, bgZ, bgdZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
414  minNumberGridPoints = min(len(set(bgX)), len(set(bgY)))
415  if len(bgZ) == 0:
416  raise ValueError("No overlap with reference. Nothing to match")
417  elif minNumberGridPoints <= self.config.order:
418  # must either lower order or raise number of bins or throw exception
419  if self.config.undersampleStyle == "THROW_EXCEPTION":
420  raise ValueError("Image does not cover enough of ref image for order and binsize")
421  elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
422  self.log.warn("Reducing order to %d"%(minNumberGridPoints - 1))
423  order = minNumberGridPoints - 1
424  elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
425  newBinSize = (minNumberGridPoints*self.config.binSize) // (self.config.order + 1)
426  bctrl.setNxSample(newBinSize)
427  bctrl.setNySample(newBinSize)
428  bkgd = afwMath.makeBackground(diffMI, bctrl) # do over
429  self.log.warn("Decreasing binsize to %d"%(newBinSize))
430 
431  # If there is no variance in any image pixels, do not weight bins by inverse variance
432  isUniformImageDiff = not numpy.any(bgdZ > self.config.gridStdevEpsilon)
433  weightByInverseVariance = False if isUniformImageDiff else self.config.approxWeighting
434 
435  # Add offset to sciExposure
436  try:
437  if self.config.usePolynomial:
438  actrl = afwMath.ApproximateControl(afwMath.ApproximateControl.CHEBYSHEV,
439  order, order, weightByInverseVariance)
440  undersampleStyle = getattr(afwMath, self.config.undersampleStyle)
441  approx = bkgd.getApproximate(actrl, undersampleStyle)
442  bkgdImage = approx.getImage()
443  else:
444  bkgdImage = bkgd.getImageF()
445  except Exception as e:
446  raise RuntimeError("Background/Approximation failed to interp image %s: %s" % (
447  self.debugDataIdString, e))
448 
449  sciMI = sciExposure.getMaskedImage()
450  sciMI += bkgdImage
451  del sciMI
452 
453  # Need RMS from fit: 2895 will replace this:
454  rms = 0.0
455  X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
456  x0, y0 = diffMI.getXY0()
457  modelValueArr = numpy.empty(len(Z))
458  for i in range(len(X)):
459  modelValueArr[i] = bkgdImage.get(int(X[i]-x0), int(Y[i]-y0))
460  resids = Z - modelValueArr
461  rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))
462 
463  if lsstDebug.Info(__name__).savefits:
464  sciExposure.writeFits(lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits')
465 
466  if lsstDebug.Info(__name__).savefig:
467  bbox = afwGeom.Box2D(refExposure.getMaskedImage().getBBox())
468  try:
469  self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr, resids)
470  except Exception as e:
471  self.log.warn('Debug plot not generated: %s'%(e))
472 
473  meanVar = afwMath.makeStatistics(diffMI.getVariance(), diffMI.getMask(),
474  afwMath.MEANCLIP, self.sctrl).getValue()
475 
476  diffIm = diffMI.getImage()
477  diffIm -= bkgdImage # diffMI should now have a mean ~ 0
478  del diffIm
479  mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE, self.sctrl).getValue()
480 
481  outBkgd = approx if self.config.usePolynomial else bkgd
482 
483  return pipeBase.Struct(
484  backgroundModel=outBkgd,
485  fitRMS=rms,
486  matchedMSE=mse,
487  diffImVar=meanVar)
488 
489  def _debugPlot(self, X, Y, Z, dZ, modelImage, bbox, model, resids):
490  """Generate a plot showing the background fit and residuals.
491 
492  It is called when lsstDebug.Info(__name__).savefig = True
493  Saves the fig to lsstDebug.Info(__name__).figpath
494  Displays on screen if lsstDebug.Info(__name__).display = True
495 
496  @param X: array of x positions
497  @param Y: array of y positions
498  @param Z: array of the grid values that were interpolated
499  @param dZ: array of the error on the grid values
500  @param modelImage: image ofthe model of the fit
501  @param model: array of len(Z) containing the grid values predicted by the model
502  @param resids: Z - model
503  """
504  import matplotlib.pyplot as plt
505  import matplotlib.colors
506  from mpl_toolkits.axes_grid1 import ImageGrid
507  zeroIm = afwImage.MaskedImageF(afwGeom.Box2I(bbox))
508  zeroIm += modelImage
509  x0, y0 = zeroIm.getXY0()
510  dx, dy = zeroIm.getDimensions()
511  if len(Z) == 0:
512  self.log.warn("No grid. Skipping plot generation.")
513  else:
514  max, min = numpy.max(Z), numpy.min(Z)
515  norm = matplotlib.colors.normalize(vmax=max, vmin=min)
516  maxdiff = numpy.max(numpy.abs(resids))
517  diffnorm = matplotlib.colors.normalize(vmax=maxdiff, vmin=-maxdiff)
518  rms = numpy.sqrt(numpy.mean(resids**2))
519  fig = plt.figure(1, (8, 6))
520  meanDz = numpy.mean(dZ)
521  grid = ImageGrid(fig, 111, nrows_ncols=(1, 2), axes_pad=0.1,
522  share_all=True, label_mode="L", cbar_mode="each",
523  cbar_size="7%", cbar_pad="2%", cbar_location="top")
524  im = grid[0].imshow(zeroIm.getImage().getArray(),
525  extent=(x0, x0+dx, y0+dy, y0), norm=norm,
526  cmap='Spectral')
527  im = grid[0].scatter(X, Y, c=Z, s=15.*meanDz/dZ, edgecolor='none', norm=norm,
528  marker='o', cmap='Spectral')
529  im2 = grid[1].scatter(X, Y, c=resids, edgecolor='none', norm=diffnorm,
530  marker='s', cmap='seismic')
531  grid.cbar_axes[0].colorbar(im)
532  grid.cbar_axes[1].colorbar(im2)
533  grid[0].axis([x0, x0+dx, y0+dy, y0])
534  grid[1].axis([x0, x0+dx, y0+dy, y0])
535  grid[0].set_xlabel("model and grid")
536  grid[1].set_xlabel("residuals. rms = %0.3f"%(rms))
537  if lsstDebug.Info(__name__).savefig:
538  fig.savefig(lsstDebug.Info(__name__).figpath + self.debugDataIdString + '.png')
539  if lsstDebug.Info(__name__).display:
540  plt.show()
541  plt.clf()
542 
543  def _gridImage(self, maskedImage, binsize, statsFlag):
544  """Private method to grid an image for debugging"""
545  width, height = maskedImage.getDimensions()
546  x0, y0 = maskedImage.getXY0()
547  xedges = numpy.arange(0, width, binsize)
548  yedges = numpy.arange(0, height, binsize)
549  xedges = numpy.hstack((xedges, width)) # add final edge
550  yedges = numpy.hstack((yedges, height)) # add final edge
551 
552  # Use lists/append to protect against the case where
553  # a bin has no valid pixels and should not be included in the fit
554  bgX = []
555  bgY = []
556  bgZ = []
557  bgdZ = []
558 
559  for ymin, ymax in zip(yedges[0:-1], yedges[1:]):
560  for xmin, xmax in zip(xedges[0:-1], xedges[1:]):
561  subBBox = afwGeom.Box2I(afwGeom.PointI(int(x0 + xmin), int(y0 + ymin)),
562  afwGeom.PointI(int(x0 + xmax-1), int(y0 + ymax-1)))
563  subIm = afwImage.MaskedImageF(maskedImage, subBBox, afwImage.PARENT, False)
564  stats = afwMath.makeStatistics(subIm,
565  afwMath.MEAN | afwMath.MEANCLIP | afwMath.MEDIAN |
566  afwMath.NPOINT | afwMath.STDEV,
567  self.sctrl)
568  npoints, _ = stats.getResult(afwMath.NPOINT)
569  if npoints >= 2:
570  stdev, _ = stats.getResult(afwMath.STDEV)
571  if stdev < self.config.gridStdevEpsilon:
572  stdev = self.config.gridStdevEpsilon
573  bgX.append(0.5 * (x0 + xmin + x0 + xmax))
574  bgY.append(0.5 * (y0 + ymin + y0 + ymax))
575  bgdZ.append(stdev/numpy.sqrt(npoints))
576  est, _ = stats.getResult(statsFlag)
577  bgZ.append(est)
578 
579  return numpy.array(bgX), numpy.array(bgY), numpy.array(bgZ), numpy.array(bgdZ)
580 
581 
582 class DataRefMatcher(object):
583  """Match data references for a specified dataset type
584 
585  Note that this is not exact, but should suffice for this task
586  until there is better support for this kind of thing in the butler.
587  """
588 
589  def __init__(self, butler, datasetType):
590  """Construct a DataRefMatcher
591 
592  @param[in] butler
593  @param[in] datasetType: dataset type to match
594  """
595  self._datasetType = datasetType # for diagnostics
596  self._keyNames = butler.getKeys(datasetType)
597 
598  def _makeKey(self, ref):
599  """Return a tuple of values for the specified keyNames
600 
601  @param[in] ref: data reference
602 
603  @raise KeyError if ref.dataId is missing a key in keyNames
604  """
605  return tuple(ref.dataId[key] for key in self._keyNames)
606 
607  def isMatch(self, ref0, ref1):
608  """Return True if ref0 == ref1
609 
610  @param[in] ref0: data ref 0
611  @param[in] ref1: data ref 1
612 
613  @raise KeyError if either ID is missing a key in keyNames
614  """
615  return self._makeKey(ref0) == self._makeKey(ref1)
616 
617  def matchList(self, ref0, refList):
618  """Return a list of indices of matches
619 
620  @return tuple of indices of matches
621 
622  @raise KeyError if any ID is missing a key in keyNames
623  """
624  key0 = self._makeKey(ref0)
625  return tuple(ind for ind, ref in enumerate(refList) if self._makeKey(ref) == key0)
boost::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:467
Pass parameters to a Background object.
Definition: Background.h:61
An integer coordinate rectangle.
Definition: Box.h:53
Control how to make an approximation.
Definition: Approximate.h:47
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
Definition: Statistics.h:92
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1107
A floating-point coordinate rectangle geometry.
Definition: Box.h:271