ConfigClass = MatchBackgroundsConfig
config: MatchBackgroundsConfig
_DefaultName = "matchBackgrounds"
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
# Fits on binned images only; masking controlled in tractBackground.py
self.statsFlag = stringToStatisticsProperty(self.config.gridStatistic)
self.statsCtrl = StatisticsControl()
self.statsCtrl.setNanSafe(True)
self.statsCtrl.setNumSigmaClip(self.config.numSigmaClip)
self.statsCtrl.setNumIter(self.config.numIter)
self.stringToInterpStyle = stringToInterpStyle(self.config.interpStyle)
self.undersampleStyle = stringToUndersampleStyle(self.config.undersampleStyle)
self.makeSubtask("reference")
@timeMethod
def run(self, warps, skyMap):
# TODO: include warped backgroundToPhotometricRatio correction
if (numExp := len(warps)) < 1:
self.log.warning("No exposures found! Returning empty lists.")
return Struct(backgroundInfoList=[], matchedImageList=[])
if self.config.refWarpVisit is None:
# Build FFP BG models of each visit
visitTractBgs = self.reference._makeTractBackgrounds(warps, skyMap)
# Choose a reference visit using those
refVisId = self.reference._defineWarps(visitTractBgs)
else:
self.log.info("Using user-supplied reference visit %d", self.config.refWarpVisit)
refVisId = self.config.refWarpVisit
self.log.info("Matching %d Exposures", numExp)
backgroundInfoList, matchedImageList = self.matchBackgrounds(warps, skyMap, refVisId)
return Struct(backgroundInfoList=backgroundInfoList, matchedImageList=matchedImageList)
@timeMethod
def _makeTractDifferenceBackgrounds(self, warps, skyMap, refVisitId):
# First, separate warps by visit
visits = np.unique([i.dataId["visit"] for i in warps])
# Then build difference image background models for each visit & store
visitTractDifferenceBackgrounds = {}
for i in range(len(visits)):
visitWarpDDFs = [j for j in warps if j.dataId["visit"] == visits[i]]
refWarpDDFs = [j for j in warps if j.dataId["visit"] == refVisitId]
refPatches = [j.dataId["patch"] for j in refWarpDDFs]
# Set up empty full tract background model object
bgModelBase = TractBackground(
config=self.config.tractBgModel, skymap=skyMap, tract=warps[0].dataId["tract"]
)
bgModels = []
for warp in visitWarpDDFs:
msg = "Constructing FFP background model for reference visit %d - visit %d using %d patches"
self.log.debug(
msg,
refVisitId,
visits[i],
len(visitWarpDDFs),
)
workingWarp = warp.get()
patchId = warp.dataId["patch"]
# On no overlap between working warp and reference visit, set
# the image to all NaN
try:
idx = refPatches.index(patchId)
refWarp = refWarpDDFs[idx].get()
except ValueError:
refWarp = workingWarp.clone()
refWarp.image += np.nan
workingWarp.image.array = refWarp.image.array - workingWarp.image.array
bgModel = bgModelBase.clone()
bgModel.addWarp(workingWarp)
bgModels.append(bgModel)
# Merge warp difference models to make a single full tract
# background difference model
for bgModel, warp in zip(bgModels, visitWarpDDFs):
msg = (
"Patch %d: Merging %d unmasked pixels (%.1f%s of detector area) into full tract "
"difference background model"
)
self.log.debug(
msg,
warp.dataId["patch"],
bgModel._numbers.getArray().sum(),
100 * bgModel._numbers.getArray().sum() / workingWarp.getBBox().getArea(),
"%",
)
bgModelBase += bgModel
# Fit full tract background to generate offset image
if visits[i] != refVisitId:
bgModelImage = bgModelBase.getStatsImage()
# Note: this just extrapolates into regions of no overlap
# between reference and visit
bkgd, _ = fitBackground(
bgModelImage, self.statsCtrl, self.statsFlag, self.config.undersampleStyle
)
try:
bkgdImage = bkgd.getImageF(self.config.interpStyle, self.config.undersampleStyle)
except Exception as e:
e.add_note(f"on image {warp.dataId}")
raise
# Calculate RMS and MSE of fit and print as log
resids = ImageF(bgModelImage.array - bkgdImage.array)
rms = np.sqrt(np.nanmean(resids.array**2))
mse = makeStatistics(resids, MEANSQUARE, self.statsCtrl).getValue()
self.log.info(
"Visit %d; difference BG fit RMS=%.2f nJy, matched MSE=%.2f nJy",
visits[i],
rms,
mse,
)
# Replace binned difference image w/best-fit model.
# Resetting numbers to override interpolation
bgModelBase._numbers.array[:] = 1e6 # Arbitrarily large value
bgModelBase._values.array = bkgdImage.array * bgModelBase._numbers.array
visitTractDifferenceBackgrounds[visits[i]] = bgModelBase
return visitTractDifferenceBackgrounds
@timeMethod
def matchBackgrounds(self, warps, skyMap, refVisitId):
Definition at line 436 of file matchBackgrounds.py.