333 Match science exposure's background level to that of reference exposure.
335 Process creates a difference image of the reference exposure minus the science exposure, and then
336 generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
337 already has detections set. If detections have not been set/masked, sources will bias the
338 background estimation.
339 The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
340 or by polynomial interpolation by the Approximate class. This model of difference image is added to the
341 science exposure in memory.
342 Fit diagnostics are also calculated and returned.
344 @param[in] refExposure: reference exposure
345 @param[in,out] sciExposure: science exposure; modified by changing the background level
346 to match that of the reference exposure
347 @returns a pipBase.Struct with fields:
348 - backgroundModel: an afw.math.Approximate or an afw.math.Background.
349 - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
350 - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
351 should be comparable to difference image's mean variance.
352 - diffImVar: the mean variance of the difference image.
356 refExposure.writeFits(
lsstDebug.Info(__name__).figpath +
'refExposure.fits')
357 sciExposure.writeFits(
lsstDebug.Info(__name__).figpath +
'sciExposure.fits')
360 if self.config.usePolynomial:
361 x, y = sciExposure.getDimensions()
362 shortSideLength = min(x, y)
363 if shortSideLength < self.config.binSize:
364 raise ValueError(
"%d = config.binSize > shorter dimension = %d" % (self.config.binSize,
366 npoints = shortSideLength // self.config.binSize
367 if shortSideLength % self.config.binSize != 0:
370 if self.config.order > npoints - 1:
371 raise ValueError(
"%d = config.order > npoints - 1 = %d" % (self.config.order, npoints - 1))
374 if (sciExposure.getDimensions() != refExposure.getDimensions()):
375 wSci, hSci = sciExposure.getDimensions()
376 wRef, hRef = refExposure.getDimensions()
378 "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)" %
379 (wSci,hSci,wRef,hRef))
381 statsFlag = getattr(afwMath, self.config.gridStatistic)
382 self.sctrl.setNumSigmaClip(self.config.numSigmaClip)
383 self.sctrl.setNumIter(self.config.numIter)
385 im = refExposure.getMaskedImage()
386 diffMI = im.Factory(im,
True)
387 diffMI -= sciExposure.getMaskedImage()
389 width = diffMI.getWidth()
390 height = diffMI.getHeight()
391 nx = width // self.config.binSize
392 if width % self.config.binSize != 0:
394 ny = height // self.config.binSize
395 if height % self.config.binSize != 0:
399 bctrl.setUndersampleStyle(self.config.undersampleStyle)
400 bctrl.setInterpStyle(self.config.interpStyle)
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)))
412 raise ValueError(
"No overlap with reference. Nothing to match")
413 elif minNumberGridPoints <= self.config.order:
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.warn(
"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)
425 self.log.warn(
"Decreasing binsize to %d"%(newBinSize))
428 isUniformImageDiff =
not numpy.any(bgdZ > self.config.gridStdevEpsilon)
429 weightByInverseVariance =
False if isUniformImageDiff
else self.config.approxWeighting
433 if self.config.usePolynomial:
435 order, order, weightByInverseVariance)
436 undersampleStyle = getattr(afwMath, self.config.undersampleStyle)
437 approx = bkgd.getApproximate(actrl,undersampleStyle)
438 bkgdImage = approx.getImage()
440 bkgdImage = bkgd.getImageF()
442 raise RuntimeError(
"Background/Approximation failed to interp image %s: %s" % (
445 sciMI = sciExposure.getMaskedImage()
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.get(int(X[i]-x0),int(Y[i]-y0))
456 resids = Z - modelValueArr
457 rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))
460 sciExposure.writeFits(
lsstDebug.Info(__name__).figpath +
'sciMatchedExposure.fits')
465 self.
_debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr, resids)
467 self.log.warn(
'Debug plot not generated: %s'%(e))
470 afwMath.MEANCLIP, self.
sctrl).getValue()
472 diffIm = diffMI.getImage()
477 outBkgd = approx
if self.config.usePolynomial
else bkgd
480 return pipeBase.Struct(
481 backgroundModel = outBkgd,