Skip to content

Commit f379682

Browse files
committed
Refactor AEW
1 parent 52660e1 commit f379682

File tree

1 file changed

+20
-170
lines changed

1 file changed

+20
-170
lines changed

python/lsst/pipe/tasks/matchBackgrounds.py

Lines changed: 20 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,12 @@
2121

2222
__all__ = ["MatchBackgroundsConnections", "MatchBackgroundsConfig", "MatchBackgroundsTask"]
2323

24-
import lsstDebug
2524
import numpy as np
26-
from lsst.afw.image import LOCAL, PARENT, ExposureF, ImageF, Mask, MaskedImageF
25+
from lsst.afw.image import LOCAL, ImageF, Mask, MaskedImageF
2726
from lsst.afw.math import (
28-
MEAN,
2927
MEANCLIP,
3028
MEANSQUARE,
31-
MEDIAN,
3229
NPOINT,
33-
STDEV,
3430
VARIANCE,
3531
ApproximateControl,
3632
BackgroundControl,
@@ -43,7 +39,6 @@
4339
stringToStatisticsProperty,
4440
stringToUndersampleStyle,
4541
)
46-
from lsst.geom import Box2D, Box2I, PointI
4742
from lsst.pex.config import ChoiceField, Field, ListField, RangeField
4843
from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections, Struct, TaskError
4944
from lsst.pipe.base.connectionTypes import Input, Output
@@ -137,7 +132,16 @@ class MatchBackgroundsConfig(PipelineTaskConfig, pipelineConnections=MatchBackgr
137132
)
138133
badMaskPlanes = ListField[str](
139134
doc="Names of mask planes to ignore while estimating the background.",
140-
default=["NO_DATA", "DETECTED", "DETECTED_NEGATIVE", "SAT", "BAD", "INTRP", "CR"],
135+
default=[
136+
"NO_DATA",
137+
"DETECTED",
138+
"DETECTED_NEGATIVE",
139+
"SAT",
140+
"BAD",
141+
"INTRP",
142+
"CR",
143+
"NOT_DEBLENDED",
144+
],
141145
itemCheck=lambda x: x in Mask().getMaskPlaneDict(),
142146
)
143147
gridStatistic = ChoiceField(
@@ -237,9 +241,6 @@ def __init__(self, *args, **kwargs):
237241
super().__init__(**kwargs)
238242
self.statsFlag = stringToStatisticsProperty(self.config.gridStatistic)
239243
self.statsCtrl = StatisticsControl()
240-
# TODO: Check that setting the mask planes here work - these planes
241-
# can vary from exposure to exposure, I think?
242-
# Aaron: I think only the bit values vary, not the names, which this is referencing.
243244
self.statsCtrl.setAndMask(Mask.getPlaneBitMask(self.config.badMaskPlanes))
244245
self.statsCtrl.setNanSafe(True)
245246
self.statsCtrl.setNumSigmaClip(self.config.numSigmaClip)
@@ -278,7 +279,7 @@ def run(self, warps):
278279
raise TaskError("No exposures to match")
279280

280281
# Define a reference warp; 'warps' is modified in-place to exclude it
281-
refWarp, refInd = self._defineWarps(warps=warps, refWarpVisit=self.config.refWarpVisit)
282+
refWarp, refInd, bkgd = self._defineWarps(warps=warps, refWarpVisit=self.config.refWarpVisit)
282283

283284
# Images must be scaled to a common ZP
284285
# Converting everything to nJy to accomplish this
@@ -287,29 +288,11 @@ def run(self, warps):
287288

288289
self.log.info("Matching %d Exposures", numExp)
289290

290-
# Creating a null BackgroundList object by fitting a blank image
291-
statsFlag = stringToStatisticsProperty(self.config.gridStatistic)
292-
self.statsCtrl.setNumSigmaClip(self.config.numSigmaClip)
293-
self.statsCtrl.setNumIter(self.config.numIter)
294-
295-
# TODO: refactor below to construct blank bg model
296-
im = refExposure.getMaskedImage()
297-
blankIm = im.clone()
298-
blankIm.image.array *= 0
299-
300-
width = blankIm.getWidth()
301-
height = blankIm.getHeight()
302-
nx = width // self.config.binSize
303-
if width % self.config.binSize != 0:
304-
nx += 1
305-
ny = height // self.config.binSize
306-
if height % self.config.binSize != 0:
307-
ny += 1
308-
309-
bctrl = BackgroundControl(nx, ny, self.statsCtrl, statsFlag)
310-
bctrl.setUndersampleStyle(self.config.undersampleStyle)
311-
312-
bkgd = makeBackground(blankIm, bctrl)
291+
# Blank ref warp background as reference background
292+
bkgdIm = bkgd.getImageF()
293+
bkgdStatsIm = bkgd.getStatsImage()
294+
bkgdIm *= 0
295+
bkgdStatsIm *= 0
313296
blank = BackgroundList(
314297
(
315298
bkgd,
@@ -325,7 +308,6 @@ def run(self, warps):
325308
backgroundInfoList = []
326309
matchedImageList = []
327310
for exp in warps:
328-
# TODO: simplify what this prints?
329311
self.log.info(
330312
"Matching background of %s to %s",
331313
exp.dataId,
@@ -347,7 +329,6 @@ def run(self, warps):
347329
toMatchExposure.image /= instFluxToNanojansky # Back to cts
348330
matchedImageList.append(toMatchExposure)
349331

350-
# TODO: more elegant solution than inserting blank model at ref ind?
351332
backgroundInfoList.insert(refInd, blank)
352333
refExposure.image /= instFluxToNanojanskyRef # Back to cts
353334
matchedImageList.insert(refInd, refExposure)
@@ -377,6 +358,8 @@ def _defineWarps(self, warps, refWarpVisit=None):
377358
Reference warped exposure.
378359
refWarpIndex : `int`
379360
Index of the reference removed from the list of warps.
361+
warpBg : `~lsst.afw.math.BackgroundMI`
362+
Temporary background model, used to make a blank BG for the ref
380363
381364
Notes
382365
-----
@@ -454,7 +437,7 @@ def _defineWarps(self, warps, refWarpVisit=None):
454437
ind = np.nanargmin(costFunctionVals)
455438
refWarp = warps.pop(ind)
456439
self.log.info("Using best reference visit %d", refWarp.dataId["visit"])
457-
return refWarp, ind
440+
return refWarp, ind, warpBg
458441

459442
def _makeBackground(self, warp: MaskedImageF, binSize) -> tuple[BackgroundMI, BackgroundControl]:
460443
"""Generate a simple binned background masked image for warped data.
@@ -528,11 +511,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
528511
model : `~lsst.afw.math.BackgroundMI`
529512
Background model of difference image, reference - science
530513
"""
531-
# TODO: this is deprecated
532-
if lsstDebug.Info(__name__).savefits:
533-
refExposure.writeFits(lsstDebug.Info(__name__).figpath + "refExposure.fits")
534-
sciExposure.writeFits(lsstDebug.Info(__name__).figpath + "sciExposure.fits")
535-
536514
# Check Configs for polynomials:
537515
if self.config.usePolynomial:
538516
x, y = sciExposure.getDimensions()
@@ -622,17 +600,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
622600
resids = bgZ - modelValueArr
623601
rms = np.sqrt(np.mean(resids[~np.isnan(resids)] ** 2))
624602

625-
# TODO: also deprecated; _gridImage() maybe can go?
626-
if lsstDebug.Info(__name__).savefits:
627-
sciExposure.writeFits(lsstDebug.Info(__name__).figpath + "sciMatchedExposure.fits")
628-
629-
if lsstDebug.Info(__name__).savefig:
630-
bbox = Box2D(refExposure.getMaskedImage().getBBox())
631-
try:
632-
self._debugPlot(bgX, bgY, bgZ, bgdZ, bkgdImage, bbox, modelValueArr, resids)
633-
except Exception as e:
634-
self.log.warning("Debug plot not generated: %s", e)
635-
636603
meanVar = makeStatistics(diffMI.getVariance(), diffMI.getMask(), MEANCLIP, self.statsCtrl).getValue()
637604

638605
diffIm = diffMI.getImage()
@@ -642,7 +609,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
642609

643610
outBkgd = approx if self.config.usePolynomial else bkgd
644611
# Convert this back into counts
645-
# TODO: is there a one-line way to do this?
646612
statsIm = outBkgd.getStatsImage()
647613
statsIm /= instFluxToNanojansky
648614
bkgdIm = outBkgd.getImageF()
@@ -667,119 +633,3 @@ def matchBackgrounds(self, refExposure, sciExposure):
667633
False,
668634
)
669635
)
670-
671-
def _debugPlot(self, X, Y, Z, dZ, modelImage, bbox, model, resids):
672-
"""
673-
Consider deleting this entirely
674-
Generate a plot showing the background fit and residuals.
675-
676-
It is called when lsstDebug.Info(__name__).savefig = True.
677-
Saves the fig to lsstDebug.Info(__name__).figpath.
678-
Displays on screen if lsstDebug.Info(__name__).display = True.
679-
680-
Parameters
681-
----------
682-
X : `np.ndarray`, (N,)
683-
Array of x positions.
684-
Y : `np.ndarray`, (N,)
685-
Array of y positions.
686-
Z : `np.ndarray`
687-
Array of the grid values that were interpolated.
688-
dZ : `np.ndarray`, (len(Z),)
689-
Array of the error on the grid values.
690-
modelImage : `Unknown`
691-
Image of the model of the fit.
692-
model : `np.ndarray`, (len(Z),)
693-
Array of len(Z) containing the grid values predicted by the model.
694-
resids : `Unknown`
695-
Z - model.
696-
"""
697-
import matplotlib.colors
698-
import matplotlib.pyplot as plt
699-
from mpl_toolkits.axes_grid1 import ImageGrid
700-
701-
zeroIm = MaskedImageF(Box2I(bbox))
702-
zeroIm += modelImage
703-
x0, y0 = zeroIm.getXY0()
704-
dx, dy = zeroIm.getDimensions()
705-
if len(Z) == 0:
706-
self.log.warning("No grid. Skipping plot generation.")
707-
else:
708-
max, min = np.max(Z), np.min(Z)
709-
norm = matplotlib.colors.normalize(vmax=max, vmin=min)
710-
maxdiff = np.max(np.abs(resids))
711-
diffnorm = matplotlib.colors.normalize(vmax=maxdiff, vmin=-maxdiff)
712-
rms = np.sqrt(np.mean(resids**2))
713-
fig = plt.figure(1, (8, 6))
714-
meanDz = np.mean(dZ)
715-
grid = ImageGrid(
716-
fig,
717-
111,
718-
nrows_ncols=(1, 2),
719-
axes_pad=0.1,
720-
share_all=True,
721-
label_mode="L",
722-
cbar_mode="each",
723-
cbar_size="7%",
724-
cbar_pad="2%",
725-
cbar_location="top",
726-
)
727-
im = grid[0].imshow(
728-
zeroIm.getImage().getArray(), extent=(x0, x0 + dx, y0 + dy, y0), norm=norm, cmap="Spectral"
729-
)
730-
im = grid[0].scatter(
731-
X, Y, c=Z, s=15.0 * meanDz / dZ, edgecolor="none", norm=norm, marker="o", cmap="Spectral"
732-
)
733-
im2 = grid[1].scatter(X, Y, c=resids, edgecolor="none", norm=diffnorm, marker="s", cmap="seismic")
734-
grid.cbar_axes[0].colorbar(im)
735-
grid.cbar_axes[1].colorbar(im2)
736-
grid[0].axis([x0, x0 + dx, y0 + dy, y0])
737-
grid[1].axis([x0, x0 + dx, y0 + dy, y0])
738-
grid[0].set_xlabel("model and grid")
739-
grid[1].set_xlabel("residuals. rms = %0.3f" % (rms))
740-
if lsstDebug.Info(__name__).savefig:
741-
fig.savefig(lsstDebug.Info(__name__).figpath + self.debugDataIdString + ".png")
742-
if lsstDebug.Info(__name__).display:
743-
plt.show()
744-
plt.clf()
745-
746-
def _gridImage(self, maskedImage, binsize, statsFlag):
747-
"""Private method to grid an image for debugging."""
748-
width, height = maskedImage.getDimensions()
749-
x0, y0 = maskedImage.getXY0()
750-
xedges = np.arange(0, width, binsize)
751-
yedges = np.arange(0, height, binsize)
752-
xedges = np.hstack((xedges, width)) # add final edge
753-
yedges = np.hstack((yedges, height)) # add final edge
754-
755-
# Use lists/append to protect against the case where
756-
# a bin has no valid pixels and should not be included in the fit
757-
bgX = []
758-
bgY = []
759-
bgZ = []
760-
bgdZ = []
761-
762-
for ymin, ymax in zip(yedges[0:-1], yedges[1:]):
763-
for xmin, xmax in zip(xedges[0:-1], xedges[1:]):
764-
subBBox = Box2I(
765-
PointI(int(x0 + xmin), int(y0 + ymin)),
766-
PointI(int(x0 + xmax - 1), int(y0 + ymax - 1)),
767-
)
768-
subIm = MaskedImageF(maskedImage, subBBox, PARENT, False)
769-
stats = makeStatistics(
770-
subIm,
771-
MEAN | MEANCLIP | MEDIAN | NPOINT | STDEV,
772-
self.statsCtrl,
773-
)
774-
npoints, _ = stats.getResult(NPOINT)
775-
if npoints >= 2:
776-
stdev, _ = stats.getResult(STDEV)
777-
if stdev < self.config.gridStdevEpsilon:
778-
stdev = self.config.gridStdevEpsilon
779-
bgX.append(0.5 * (x0 + xmin + x0 + xmax))
780-
bgY.append(0.5 * (y0 + ymin + y0 + ymax))
781-
bgdZ.append(stdev / np.sqrt(npoints))
782-
est, _ = stats.getResult(statsFlag)
783-
bgZ.append(est)
784-
785-
return np.array(bgX), np.array(bgY), np.array(bgZ), np.array(bgdZ)

0 commit comments

Comments
 (0)