Skip to content

Commit a969a20

Browse files
committed
Refactor AEW
1 parent 3210ec9 commit a969a20

File tree

1 file changed

+21
-166
lines changed

1 file changed

+21
-166
lines changed

python/lsst/pipe/tasks/matchBackgrounds.py

Lines changed: 21 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import lsstDebug
2525
import numpy as np
26-
from lsst.afw.image import LOCAL, PARENT, ExposureF, ImageF, Mask, MaskedImageF
26+
from lsst.afw.image import LOCAL, PARENT, ImageF, Mask, MaskedImageF
2727
from lsst.afw.math import (
2828
MEAN,
2929
MEANCLIP,
@@ -43,7 +43,7 @@
4343
stringToStatisticsProperty,
4444
stringToUndersampleStyle,
4545
)
46-
from lsst.geom import Box2D, Box2I, PointI
46+
from lsst.geom import Box2I, PointI
4747
from lsst.pex.config import ChoiceField, Field, ListField, RangeField
4848
from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections, Struct, TaskError
4949
from lsst.pipe.base.connectionTypes import Input, Output
@@ -137,7 +137,16 @@ class MatchBackgroundsConfig(PipelineTaskConfig, pipelineConnections=MatchBackgr
137137
)
138138
badMaskPlanes = ListField[str](
139139
doc="Names of mask planes to ignore while estimating the background.",
140-
default=["NO_DATA", "DETECTED", "DETECTED_NEGATIVE", "SAT", "BAD", "INTRP", "CR"],
140+
default=[
141+
"NO_DATA",
142+
"DETECTED",
143+
"DETECTED_NEGATIVE",
144+
"SAT",
145+
"BAD",
146+
"INTRP",
147+
"CR",
148+
"NOT_DEBLENDED",
149+
],
141150
itemCheck=lambda x: x in Mask().getMaskPlaneDict(),
142151
)
143152
gridStatistic = ChoiceField(
@@ -237,9 +246,6 @@ def __init__(self, *args, **kwargs):
237246
super().__init__(**kwargs)
238247
self.statsFlag = stringToStatisticsProperty(self.config.gridStatistic)
239248
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.
243249
self.statsCtrl.setAndMask(Mask.getPlaneBitMask(self.config.badMaskPlanes))
244250
self.statsCtrl.setNanSafe(True)
245251
self.statsCtrl.setNumSigmaClip(self.config.numSigmaClip)
@@ -278,7 +284,7 @@ def run(self, warps):
278284
raise TaskError("No exposures to match")
279285

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

283289
# Images must be scaled to a common ZP
284290
# Converting everything to nJy to accomplish this
@@ -287,29 +293,11 @@ def run(self, warps):
287293

288294
self.log.info("Matching %d Exposures", numExp)
289295

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)
296+
# Blank ref warp background as reference background
297+
bkgdIm = bkgd.getImageF()
298+
bkgdStatsIm = bkgd.getStatsImage()
299+
bkgdIm *= 0
300+
bkgdStatsIm *= 0
313301
blank = BackgroundList(
314302
(
315303
bkgd,
@@ -325,7 +313,6 @@ def run(self, warps):
325313
backgroundInfoList = []
326314
matchedImageList = []
327315
for exp in warps:
328-
# TODO: simplify what this prints?
329316
self.log.info(
330317
"Matching background of %s to %s",
331318
exp.dataId,
@@ -347,7 +334,6 @@ def run(self, warps):
347334
toMatchExposure.image /= instFluxToNanojansky # Back to cts
348335
matchedImageList.append(toMatchExposure)
349336

350-
# TODO: more elegant solution than inserting blank model at ref ind?
351337
backgroundInfoList.insert(refInd, blank)
352338
refExposure.image /= instFluxToNanojanskyRef # Back to cts
353339
matchedImageList.insert(refInd, refExposure)
@@ -377,6 +363,8 @@ def _defineWarps(self, warps, refWarpVisit=None):
377363
Reference warped exposure.
378364
refWarpIndex : `int`
379365
Index of the reference removed from the list of warps.
366+
warpBg : `~lsst.afw.math.BackgroundMI`
367+
Temporary background model, used to make a blank BG for the ref
380368
381369
Notes
382370
-----
@@ -454,7 +442,7 @@ def _defineWarps(self, warps, refWarpVisit=None):
454442
ind = np.nanargmin(costFunctionVals)
455443
refWarp = warps.pop(ind)
456444
self.log.info("Using best reference visit %d", refWarp.dataId["visit"])
457-
return refWarp, ind
445+
return refWarp, ind, warpBg
458446

459447
def _makeBackground(self, warp: MaskedImageF, binSize) -> tuple[BackgroundMI, BackgroundControl]:
460448
"""Generate a simple binned background masked image for warped data.
@@ -528,11 +516,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
528516
model : `~lsst.afw.math.BackgroundMI`
529517
Background model of difference image, reference - science
530518
"""
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-
536519
# Check Configs for polynomials:
537520
if self.config.usePolynomial:
538521
x, y = sciExposure.getDimensions()
@@ -622,17 +605,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
622605
resids = bgZ - modelValueArr
623606
rms = np.sqrt(np.mean(resids[~np.isnan(resids)] ** 2))
624607

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-
636608
meanVar = makeStatistics(diffMI.getVariance(), diffMI.getMask(), MEANCLIP, self.statsCtrl).getValue()
637609

638610
diffIm = diffMI.getImage()
@@ -642,7 +614,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
642614

643615
outBkgd = approx if self.config.usePolynomial else bkgd
644616
# Convert this back into counts
645-
# TODO: is there a one-line way to do this?
646617
statsIm = outBkgd.getStatsImage()
647618
statsIm /= instFluxToNanojansky
648619
bkgdIm = outBkgd.getImageF()
@@ -667,119 +638,3 @@ def matchBackgrounds(self, refExposure, sciExposure):
667638
False,
668639
)
669640
)
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)