363 def matchBackgrounds(self, refExposure, sciExposure):
364 """Match science exposure's background level to that of reference exposure.
365
366 Process creates a difference image of the reference exposure minus the science exposure, and then
367 generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
368 already has detections set. If detections have not been set/masked, sources will bias the
369 background estimation.
370
371 The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
372 or by polynomial interpolation by the Approximate class. This model of difference image
373 is added to the science exposure in memory.
374
375 Fit diagnostics are also calculated and returned.
376
377 Parameters
378 ----------
379 refExposure : `lsst.afw.image.Exposure`
380 Reference exposure.
381 sciExposure : `lsst.afw.image.Exposure`
382 Science exposure; modified by changing the background level
383 to match that of the reference exposure.
384
385 Returns
386 -------
387 model : `lsst.pipe.base.Struct`
388 Background model as a struct with attributes:
389
390 ``backgroundModel``
391 An afw.math.Approximate or an afw.math.Background.
392 ``fitRMS``
393 RMS of the fit. This is the sqrt(mean(residuals**2)), (`float`).
394 ``matchedMSE``
395 The MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
396 should be comparable to difference image's mean variance (`float`).
397 ``diffImVar``
398 The mean variance of the difference image (`float`).
399 """
401 refExposure.writeFits(
lsstDebug.Info(__name__).figpath +
'refExposure.fits')
402 sciExposure.writeFits(
lsstDebug.Info(__name__).figpath +
'sciExposure.fits')
403
404
405 if self.config.usePolynomial:
406 x, y = sciExposure.getDimensions()
407 shortSideLength =
min(x, y)
408 if shortSideLength < self.config.binSize:
409 raise ValueError("%d = config.binSize > shorter dimension = %d" % (self.config.binSize,
410 shortSideLength))
411 npoints = shortSideLength // self.config.binSize
412 if shortSideLength % self.config.binSize != 0:
413 npoints += 1
414
415 if self.config.order > npoints - 1:
416 raise ValueError("%d = config.order > npoints - 1 = %d" % (self.config.order, npoints - 1))
417
418
419 if (sciExposure.getDimensions() != refExposure.getDimensions()):
420 wSci, hSci = sciExposure.getDimensions()
421 wRef, hRef = refExposure.getDimensions()
422 raise RuntimeError(
423 "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)" %
424 (wSci, hSci, wRef, hRef))
425
426 statsFlag = getattr(afwMath, self.config.gridStatistic)
427 self.sctrl.setNumSigmaClip(self.config.numSigmaClip)
428 self.sctrl.setNumIter(self.config.numIter)
429
430 im = refExposure.getMaskedImage()
431 diffMI = im.Factory(im, True)
432 diffMI -= sciExposure.getMaskedImage()
433
434 width = diffMI.getWidth()
435 height = diffMI.getHeight()
436 nx = width // self.config.binSize
437 if width % self.config.binSize != 0:
438 nx += 1
439 ny = height // self.config.binSize
440 if height % self.config.binSize != 0:
441 ny += 1
442
444 bctrl.setUndersampleStyle(self.config.undersampleStyle)
445
447
448
449
450
451 if self.config.usePolynomial:
452 order = self.config.order
453 bgX, bgY, bgZ, bgdZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
454 minNumberGridPoints =
min(len(
set(bgX)), len(
set(bgY)))
455 if len(bgZ) == 0:
456 raise ValueError("No overlap with reference. Nothing to match")
457 elif minNumberGridPoints <= self.config.order:
458
459 if self.config.undersampleStyle == "THROW_EXCEPTION":
460 raise ValueError("Image does not cover enough of ref image for order and binsize")
461 elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
462 self.log.warning("Reducing order to %d", (minNumberGridPoints - 1))
463 order = minNumberGridPoints - 1
464 elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
465 newBinSize = (minNumberGridPoints*self.config.binSize) // (self.config.order + 1)
466 bctrl.setNxSample(newBinSize)
467 bctrl.setNySample(newBinSize)
469 self.log.warning("Decreasing binsize to %d", newBinSize)
470
471
472 isUniformImageDiff = not numpy.any(bgdZ > self.config.gridStdevEpsilon)
473 weightByInverseVariance = False if isUniformImageDiff else self.config.approxWeighting
474
475
476 try:
477 if self.config.usePolynomial:
479 order, order, weightByInverseVariance)
480 undersampleStyle = getattr(afwMath, self.config.undersampleStyle)
481 approx = bkgd.getApproximate(actrl, undersampleStyle)
482 bkgdImage = approx.getImage()
483 else:
484 bkgdImage = bkgd.getImageF(self.config.interpStyle, self.config.undersampleStyle)
485 except Exception as e:
486 raise RuntimeError("Background/Approximation failed to interp image %s: %s" % (
487 self.debugDataIdString, e))
488
489 sciMI = sciExposure.getMaskedImage()
490 sciMI += bkgdImage
491 del sciMI
492
493
494 rms = 0.0
495 X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
496 x0, y0 = diffMI.getXY0()
497 modelValueArr = numpy.empty(len(Z))
498 for i in range(len(X)):
499 modelValueArr[i] = bkgdImage[int(X[i]-x0), int(Y[i]-y0), afwImage.LOCAL]
500 resids = Z - modelValueArr
501 rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))
502
504 sciExposure.writeFits(
lsstDebug.Info(__name__).figpath +
'sciMatchedExposure.fits')
505
507 bbox =
geom.Box2D(refExposure.getMaskedImage().getBBox())
508 try:
509 self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr, resids)
510 except Exception as e:
511 self.log.warning('Debug plot not generated: %s', e)
512
514 afwMath.MEANCLIP, self.sctrl).getValue()
515
516 diffIm = diffMI.getImage()
517 diffIm -= bkgdImage
518 del diffIm
520
521 outBkgd = approx if self.config.usePolynomial else bkgd
522
523 return pipeBase.Struct(
524 backgroundModel=outBkgd,
525 fitRMS=rms,
526 matchedMSE=mse,
527 diffImVar=meanVar)
528
Control how to make an approximation.
Pass parameters to a Background object.
A floating-point coordinate rectangle geometry.
daf::base::PropertySet * set
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.