LSST Applications g00274db5b6+edbf708997,g00d0e8bbd7+edbf708997,g199a45376c+5137f08352,g1fd858c14a+1d4b6db739,g262e1987ae+f4d9505c4f,g29ae962dfc+7156fb1a53,g2cef7863aa+73c82f25e4,g35bb328faa+edbf708997,g3e17d7035e+5b3adc59f5,g3fd5ace14f+852fa6fbcb,g47891489e3+6dc8069a4c,g53246c7159+edbf708997,g64539dfbff+9f17e571f4,g67b6fd64d1+6dc8069a4c,g74acd417e5+ae494d68d9,g786e29fd12+af89c03590,g7ae74a0b1c+a25e60b391,g7aefaa3e3d+536efcc10a,g7cc15d900a+d121454f8d,g87389fa792+a4172ec7da,g89139ef638+6dc8069a4c,g8d7436a09f+28c28d8d6d,g8ea07a8fe4+db21c37724,g92c671f44c+9f17e571f4,g98df359435+b2e6376b13,g99af87f6a8+b0f4ad7b8d,gac66b60396+966efe6077,gb88ae4c679+7dec8f19df,gbaa8f7a6c5+38b34f4976,gbf99507273+edbf708997,gc24b5d6ed1+9f17e571f4,gca7fc764a6+6dc8069a4c,gcc769fe2a4+97d0256649,gd7ef33dd92+6dc8069a4c,gdab6d2f7ff+ae494d68d9,gdbb4c4dda9+9f17e571f4,ge410e46f29+6dc8069a4c,geaed405ab2+e194be0d2b,w.2025.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
matchBackgrounds.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = [
23 "MatchBackgroundsConnections",
24 "MatchBackgroundsConfig",
25 "MatchBackgroundsTask",
26 "ChooseReferenceVisitConfig",
27 "ChooseReferenceVisitTask",
28]
29
30import numpy as np
31
32from lsst.afw.image import ImageF, MaskedImageF
33from lsst.afw.math import (
34 MEANSQUARE,
35 NPOINT,
36 VARIANCE,
37 ApproximateControl,
38 BackgroundControl,
39 BackgroundMI,
40 StatisticsControl,
41 makeBackground,
42 makeStatistics,
43 stringToInterpStyle,
44 stringToStatisticsProperty,
45 stringToUndersampleStyle,
46)
47from lsst.pex.config import ChoiceField, Config, ConfigField, ConfigurableField, Field, RangeField
48from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections, Struct, Task
49from lsst.pipe.base.connectionTypes import Input, Output
50from lsst.pipe.tasks.tractBackground import TractBackground, TractBackgroundConfig
51from lsst.skymap import BaseSkyMap
52from lsst.utils.timer import timeMethod
53
54
56
57 tractBgModel = ConfigField(
58 dtype=TractBackgroundConfig,
59 doc="Background model for the entire tract",
60 )
61 bestRefWeightChi2 = RangeField(
62 dtype=float,
63 doc="Mean background goodness of fit statistic weight when calculating the best reference exposure. "
64 "Higher weights prefer exposures with flatter backgrounds. Ignored when ref visit supplied.",
65 default=0.3,
66 min=0.0,
67 max=1.0,
68 )
69 bestRefWeightVariance = RangeField(
70 dtype=float,
71 doc="Image variance weight when calculating the best reference exposure. "
72 "Higher weights prefers exposures with low image variances. Ignored when ref visit supplied.",
73 default=0.3,
74 min=0.0,
75 max=1.0,
76 )
77 bestRefWeightGlobalCoverage = RangeField(
78 dtype=float,
79 doc="Global coverage weight (total number of valid pixels) when calculating the best reference "
80 "exposure. Higher weights prefer exposures with high coverage. Ignored when ref visit supplied.",
81 default=0.4,
82 min=0.0,
83 max=1.0,
84 )
85 gridStatistic = ChoiceField(
86 dtype=str,
87 doc="Type of statistic to estimate pixel value for the grid points",
88 default="MEANCLIP",
89 allowed={"MEAN": "mean", "MEDIAN": "median", "MEANCLIP": "clipped mean"},
90 )
91 undersampleStyle = ChoiceField(
92 dtype=str,
93 doc="Behavior if there are too few points in the grid for requested interpolation style",
94 default="REDUCE_INTERP_ORDER",
95 allowed={
96 "THROW_EXCEPTION": "throw an exception if there are too few points.",
97 "REDUCE_INTERP_ORDER": "use an interpolation style with a lower order.",
98 },
99 )
100 interpStyle = ChoiceField(
101 dtype=str,
102 doc="Algorithm to interpolate the background values; ignored if ``usePolynomial=True``."
103 "Maps to an enum; see afw.math.Background for more information.",
104 default="AKIMA_SPLINE",
105 allowed={
106 "CONSTANT": "Use a single constant value.",
107 "LINEAR": "Use linear interpolation.",
108 "NATURAL_SPLINE": "A cubic spline with zero second derivative at endpoints.",
109 "AKIMA_SPLINE": "A higher-level non-linear spline that is more robust to outliers.",
110 "NONE": "No background estimation is to be attempted.",
111 },
112 )
113 numSigmaClip = Field[int](
114 doc="Sigma for outlier rejection. Ignored if ``gridStatistic != 'MEANCLIP'``.",
115 default=3,
116 )
117 numIter = Field[int](
118 doc="Number of iterations of outlier rejection. Ignored if ``gridStatistic != 'MEANCLIP'``.",
119 default=3,
120 )
121
122
124 """Select a reference visit from a list of visits by minimizing a cost
125 function
126
127 Notes
128 -----
129 The reference exposure is chosen from the list of science exposures by
130 minimizing a cost function that penalizes high background complexity
131 (divergence from a plane), high variance, and low global coverage.
132 The cost function is a weighted sum of these three metrics.
133 The weights are set by the config parameters:
134
135 - ``bestRefWeightChi2``
136 - ``bestRefWeightVariance``
137 - ``bestRefWeightGlobalCoverage``
138 """
139
140 # TODO: this is only used to select reference visits, so may become
141 # redundant if we find a means to build reference images instead.
142 ConfigClass = ChooseReferenceVisitConfig
143 config: ChooseReferenceVisitConfig
144
145 def __init__(self, *args, **kwargs):
146 super().__init__(**kwargs)
147 # Fits on binned images only; masking controlled in tractBackground.py
148 self.statsFlag = stringToStatisticsProperty(self.config.gridStatistic)
150 self.statsCtrl.setNanSafe(True)
151 self.statsCtrl.setNumSigmaClip(self.config.numSigmaClip)
152 self.statsCtrl.setNumIter(self.config.numIter)
153 self.stringToInterpStyle = stringToInterpStyle(self.config.interpStyle)
154 self.undersampleStyle = stringToUndersampleStyle(self.config.undersampleStyle)
155
156 @timeMethod
157 def _makeTractBackgrounds(self, warps, skyMap):
158 """Create full tract models of all visit backgrounds
159
160 Parameters
161 ----------
162 warps : `list`[`~lsst.daf.butler.DeferredDatasetHandle`]
163 List of warped exposures (of type `~lsst.afw.image.ExposureF`).
164 This is ordered by patch ID, then by visit ID
165 skyMap : `lsst.skyMap.SkyMap`
166 SkyMap for deriving the patch/tract dimensions
167
168 Returns
169 -------
170 visitTractBackrounds : `dict` [`int`, `TractBackground`]
171 Models of full tract backgrounds for all visits, in nanojanskies.
172 Accessed by visit ID.
173 """
174 # First, separate warps by visit
175 visits = np.unique([i.dataId["visit"] for i in warps])
176
177 # Then build background models for each visit and store
178 visitTractBackgrounds = {}
179 for i in range(len(visits)):
180 visitWarpDDFs = [j for j in warps if j.dataId["visit"] == visits[i]]
181 # Set up empty full tract background model object
182 bgModelBase = TractBackground(
183 config=self.config.tractBgModel, skymap=skyMap, tract=warps[0].dataId["tract"]
184 )
185
186 bgModels = []
187 for warp in visitWarpDDFs:
188 msg = "Constructing FFP background model for visit %d using %d patches"
189 self.log.debug(
190 msg,
191 visits[i],
192 len(visitWarpDDFs),
193 )
194 workingWarp = warp.get()
195
196 bgModel = bgModelBase.clone()
197 bgModel.addWarp(workingWarp)
198 bgModels.append(bgModel)
199
200 # Merge warp models to make a single full tract background model
201 for bgModel, warp in zip(bgModels, visitWarpDDFs):
202 msg = (
203 "Patch %d: Merging %d unmasked pixels (%.1f%s of detector area) into full tract "
204 "background model"
205 )
206 self.log.debug(
207 msg,
208 warp.dataId["patch"],
209 bgModel._numbers.getArray().sum(),
210 100 * bgModel._numbers.getArray().sum() / workingWarp.getBBox().getArea(),
211 "%",
212 )
213 bgModelBase += bgModel
214
215 visitTractBackgrounds[visits[i]] = bgModelBase
216
217 return visitTractBackgrounds
218
219 @timeMethod
220 def _defineWarps(self, visitTractBackgrounds):
221 """Define the reference visit
222
223 This method calculates an appropriate reference visit from the
224 supplied full tract visit backgrounds by minimizing a cost function
225 that penalizes high background complexity (divergence from a plane),
226 high variance within the warps comprising the visit, and low global
227 coverage.
228
229 Parameters
230 ----------
231 visitTractBackrounds : `dict` [`int`, `TractBackground`]
232 Models of full tract backgrounds for all visits, in nanojanskies.
233 Accessed by visit ID.
234 Returns
235 -------
236 refVisId : `int`
237 ID of the reference visit.
238 """
239 # Extract mean/var/npoints for each visit background model
240 fitChi2s = [] # Background goodness of fit
241 visitVars = [] # Variance of original image
242 fitNPointsGlobal = [] # Global coverage
243 visits = [] # To ensure dictionary key order is correct
244 for vis in visitTractBackgrounds:
245 visits.append(vis)
246 # Fit a polynomial model to the full tract plane
247 tractBg = visitTractBackgrounds[vis].getStatsImage()
248 tractVar = visitTractBackgrounds[vis].getVarianceImage()
249 fitBg, _ = fitBackground(tractBg, self.statsCtrl, self.statsFlag, self.config.undersampleStyle)
250 # Weight the approximation fit by the binned image variance
251 fitBg.getStatsImage().variance = tractVar
252
253 # Return an approximation to the background
254 approxCtrl = ApproximateControl(ApproximateControl.CHEBYSHEV, 1, 1, True)
255 fitApprox = fitBg.getApproximate(approxCtrl, self.undersampleStyle)
256
257 fitBgSub = MaskedImageF(ImageF(tractBg.array - fitApprox.getImage().array))
258 bad_mask_bit_mask = fitBgSub.mask.getPlaneBitMask("BAD")
259 fitBgSub.mask.array[np.isnan(fitBgSub.image.array)] = bad_mask_bit_mask
260
261 fitStats = makeStatistics(fitBgSub.image, fitBgSub.mask, VARIANCE | NPOINT, self.statsCtrl)
262
263 good = (fitBgSub.mask.array.astype(int) & bad_mask_bit_mask) == 0
264 dof = len(good[good]) - 6 # Assuming eq. of plane
265 fitChi2 = (
266 np.nansum(fitBgSub.image.array[good] ** 2 / fitBg.getStatsImage().variance.array[good]) / dof
267 )
268
269 visitVar = np.nanmean(fitBg.getStatsImage().variance.array[good])
270 fitNPointGlobal, _ = fitStats.getResult(NPOINT)
271 fitChi2s.append(fitChi2)
272 visitVars.append(visitVar)
273 fitNPointsGlobal.append(int(fitNPointGlobal))
274
275 self.log.info(
276 "Sci exp. visit %d; BG fit Chi^2=%.2e, var=%.2f nJy, nPoints global=%d",
277 vis,
278 fitChi2,
279 visitVar,
280 fitNPointGlobal,
281 )
282 # Normalize mean/var/npoints to range from 0 to 1
283 fitChi2sFrac = np.array(fitChi2s) / np.nanmax(fitChi2s)
284 fitVarsFrac = np.array(visitVars) / np.nanmax(visitVars)
285 fitNPointsGlobalFrac = np.nanmin(fitNPointsGlobal) / np.array(fitNPointsGlobal)
286
287 # Calculate cost function values
288 costFunctionVals = self.config.bestRefWeightChi2 * fitChi2sFrac
289 costFunctionVals += self.config.bestRefWeightVariance * fitVarsFrac
290 costFunctionVals += self.config.bestRefWeightGlobalCoverage * fitNPointsGlobalFrac
291
292 ind = np.nanargmin(costFunctionVals)
293 refVisitId = visits[ind]
294 self.log.info("Using best reference visit %d", refVisitId)
295
296 return refVisitId
297
298
300 PipelineTaskConnections,
301 dimensions=("skymap", "tract", "band"),
302 defaultTemplates={
303 "warpType": "psf_matched",
304 "warpTypeSuffix": "",
305 },
306):
307 # TODO: add connection for warped backgroundToPhotometricRatio
308 warps = Input(
309 doc=("Warps used to construct a list of matched backgrounds."),
310 name="{warpType}_warp",
311 storageClass="ExposureF",
312 dimensions=("skymap", "tract", "patch", "visit"),
313 deferLoad=True,
314 multiple=True,
315 )
316 skyMap = Input(
317 doc="Input definition of geometry/bbox and projection/wcs for warped exposures",
318 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
319 storageClass="SkyMap",
320 dimensions=("skymap",),
321 )
322 backgroundInfoList = Output(
323 doc="List of differential backgrounds, with goodness of fit params.",
324 name="{warpType}_warp_background_diff", # TODO: settle on appropriate name
325 dimensions=("skymap", "tract", "visit", "patch"),
326 storageClass="Background",
327 multiple=True,
328 )
329 matchedImageList = Output(
330 doc="List of background-matched warps.",
331 name="{warpType}_warp_background_matched", # TODO: settle on appropriate name
332 storageClass="ExposureF",
333 dimensions=("skymap", "tract", "visit", "patch"),
334 multiple=True,
335 )
336
337
338class MatchBackgroundsConfig(PipelineTaskConfig, pipelineConnections=MatchBackgroundsConnections):
339
340 refWarpVisit = Field[int](
341 doc="Reference visit ID. If None, the best visit is chosen using the list of visits.",
342 optional=True,
343 )
344 tractBgModel = ConfigField(
345 dtype=TractBackgroundConfig,
346 doc="Background model for the entire tract",
347 )
348 gridStatistic = ChoiceField(
349 dtype=str,
350 doc="Type of statistic to estimate pixel value for the grid points",
351 default="MEANCLIP",
352 allowed={"MEAN": "mean", "MEDIAN": "median", "MEANCLIP": "clipped mean"},
353 )
354 undersampleStyle = ChoiceField(
355 dtype=str,
356 doc="Behavior if there are too few points in the grid for requested interpolation style",
357 default="REDUCE_INTERP_ORDER",
358 allowed={
359 "THROW_EXCEPTION": "throw an exception if there are too few points.",
360 "REDUCE_INTERP_ORDER": "use an interpolation style with a lower order.",
361 },
362 )
363 interpStyle = ChoiceField(
364 dtype=str,
365 doc="Algorithm to interpolate the background values. "
366 "Maps to an enum; see afw.math.Background for more information.",
367 default="AKIMA_SPLINE",
368 allowed={
369 "CONSTANT": "Use a single constant value.",
370 "LINEAR": "Use linear interpolation.",
371 "NATURAL_SPLINE": "A cubic spline with zero second derivative at endpoints.",
372 "AKIMA_SPLINE": "A higher-level non-linear spline that is more robust to outliers.",
373 "NONE": "No background estimation is to be attempted.",
374 },
375 )
376 numSigmaClip = Field[int](
377 doc="Sigma for outlier rejection. Ignored if ``gridStatistic != 'MEANCLIP'``.",
378 default=3,
379 )
380 numIter = Field[int](
381 doc="Number of iterations of outlier rejection. Ignored if ``gridStatistic != 'MEANCLIP'``.",
382 default=3,
383 )
384 reference = ConfigurableField(
385 target=ChooseReferenceVisitTask, doc="Choose reference visit to match backgrounds to"
386 )
387
388 def validate(self):
389 if self.undersampleStyle == "INCREASE_NXNYSAMPLE":
390 raise RuntimeError("Invalid undersampleStyle: Polynomial fitting not implemented.")
391
392
393class MatchBackgroundsTask(PipelineTask):
394 """Match the backgrounds of a list of warped exposures to a reference
395
396 Attributes
397 ----------
398 config : `MatchBackgroundsConfig`
399 Configuration for this task.
400 statsCtrl : `~lsst.afw.math.StatisticsControl`
401 Statistics control object.
402
403 Notes
404 -----
405 This task is a part of the background subtraction pipeline. It matches the
406 backgrounds of a list of warped science exposures to that of a reference
407 image.
408 """
409
410 ConfigClass = MatchBackgroundsConfig
411 config: MatchBackgroundsConfig
412 _DefaultName = "matchBackgrounds"
413
414 def __init__(self, *args, **kwargs):
415 super().__init__(**kwargs)
416 # Fits on binned images only; masking controlled in tractBackground.py
417 self.statsFlag = stringToStatisticsProperty(self.config.gridStatistic)
418 self.statsCtrl = StatisticsControl()
419 self.statsCtrl.setNanSafe(True)
420 self.statsCtrl.setNumSigmaClip(self.config.numSigmaClip)
421 self.statsCtrl.setNumIter(self.config.numIter)
422 self.stringToInterpStyle = stringToInterpStyle(self.config.interpStyle)
423 self.undersampleStyle = stringToUndersampleStyle(self.config.undersampleStyle)
424
425 self.makeSubtask("reference")
426
427 @timeMethod
428 def run(self, warps, skyMap):
429 """Match the backgrounds of a list of warped exposures to the same
430 patches in a reference visit
431
432 A reference visit ID will be chosen automatically if none is supplied.
433
434 Parameters
435 ----------
436 warps : `list`[`~lsst.afw.image.Exposure`]
437 List of warped science exposures to be background-matched.
438 skyMap : `lsst.skyMap.SkyMap`
439 SkyMap for deriving the patch/tract dimensions.
440
441 Returns
442 -------
443 result : `~lsst.afw.math.BackgroundList`, `~lsst.afw.image.Exposure`
444 Differential background models and associated background-matched
445 images.
446 """
447 # TODO: include warped backgroundToPhotometricRatio correction
448 if (numExp := len(warps)) < 1:
449 self.log.warning("No exposures found! Returning empty lists.")
450 return Struct(backgroundInfoList=[], matchedImageList=[])
451
452 if self.config.refWarpVisit is None:
453 # Build FFP BG models of each visit
454 visitTractBgs = self.reference._makeTractBackgrounds(warps, skyMap)
455 # Choose a reference visit using those
456 refVisId = self.reference._defineWarps(visitTractBgs)
457 else:
458 self.log.info("Using user-supplied reference visit %d", self.config.refWarpVisit)
459 refVisId = self.config.refWarpVisit
460
461 self.log.info("Matching %d Exposures", numExp)
462
463 backgroundInfoList, matchedImageList = self.matchBackgrounds(warps, skyMap, refVisId)
464
465 return Struct(backgroundInfoList=backgroundInfoList, matchedImageList=matchedImageList)
466
467 @timeMethod
468 def _makeTractDifferenceBackgrounds(self, warps, skyMap, refVisitId):
469 """Create full tract models of all difference image backgrounds
470 (reference visit - visit)
471
472 Parameters
473 ----------
474 warps : `list`[`~lsst.daf.butler.DeferredDatasetHandle`]
475 List of warped exposures (of type `~lsst.afw.image.ExposureF`).
476 This is ordered by patch ID, then by visit ID
477 skyMap : `lsst.skyMap.SkyMap`
478 SkyMap for deriving the patch/tract dimensions.
479 refVisitId : `int`
480 Visit ID number for the chosen reference visit.
481
482 Returns
483 -------
484 visitTractDifferenceBackrounds : `dict` [`int`, `TractBackground`]
485 Models of full tract backgrounds for all difference images
486 (reference visit - visit), in nanojanskies.
487 Accessed by visit ID.
488 """
489 # First, separate warps by visit
490 visits = np.unique([i.dataId["visit"] for i in warps])
491
492 # Then build difference image background models for each visit & store
493 visitTractDifferenceBackgrounds = {}
494 for i in range(len(visits)):
495 visitWarpDDFs = [j for j in warps if j.dataId["visit"] == visits[i]]
496 refWarpDDFs = [j for j in warps if j.dataId["visit"] == refVisitId]
497 refPatches = [j.dataId["patch"] for j in refWarpDDFs]
498 # Set up empty full tract background model object
499 bgModelBase = TractBackground(
500 config=self.config.tractBgModel, skymap=skyMap, tract=warps[0].dataId["tract"]
501 )
502
503 bgModels = []
504 for warp in visitWarpDDFs:
505 msg = "Constructing FFP background model for reference visit %d - visit %d using %d patches"
506 self.log.debug(
507 msg,
508 refVisitId,
509 visits[i],
510 len(visitWarpDDFs),
511 )
512 workingWarp = warp.get()
513
514 patchId = warp.dataId["patch"]
515 # On no overlap between working warp and reference visit, set
516 # the image to all NaN
517 try:
518 idx = refPatches.index(patchId)
519 refWarp = refWarpDDFs[idx].get()
520 except ValueError:
521 refWarp = workingWarp.clone()
522 refWarp.image += np.nan
523 workingWarp.image.array = refWarp.image.array - workingWarp.image.array
524
525 bgModel = bgModelBase.clone()
526 bgModel.addWarp(workingWarp)
527 bgModels.append(bgModel)
528
529 # Merge warp difference models to make a single full tract
530 # background difference model
531 for bgModel, warp in zip(bgModels, visitWarpDDFs):
532 msg = (
533 "Patch %d: Merging %d unmasked pixels (%.1f%s of detector area) into full tract "
534 "difference background model"
535 )
536 self.log.debug(
537 msg,
538 warp.dataId["patch"],
539 bgModel._numbers.getArray().sum(),
540 100 * bgModel._numbers.getArray().sum() / workingWarp.getBBox().getArea(),
541 "%",
542 )
543 bgModelBase += bgModel
544
545 # Fit full tract background to generate offset image
546 if visits[i] != refVisitId:
547 bgModelImage = bgModelBase.getStatsImage()
548 # Note: this just extrapolates into regions of no overlap
549 # between reference and visit
550 bkgd, _ = fitBackground(
551 bgModelImage, self.statsCtrl, self.statsFlag, self.config.undersampleStyle
552 )
553 try:
554 bkgdImage = bkgd.getImageF(self.config.interpStyle, self.config.undersampleStyle)
555 except Exception as e:
556 e.add_note(f"on image {warp.dataId}")
557 raise
558 # Calculate RMS and MSE of fit and print as log
559 resids = ImageF(bgModelImage.array - bkgdImage.array)
560 rms = np.sqrt(np.nanmean(resids.array**2))
561 mse = makeStatistics(resids, MEANSQUARE, self.statsCtrl).getValue()
562
563 self.log.info(
564 "Visit %d; difference BG fit RMS=%.2f nJy, matched MSE=%.2f nJy",
565 visits[i],
566 rms,
567 mse,
568 )
569 # Replace binned difference image w/best-fit model.
570 # Resetting numbers to override interpolation
571 bgModelBase._numbers.array[:] = 1e6 # Arbitrarily large value
572 bgModelBase._values.array = bkgdImage.array * bgModelBase._numbers.array
573
574 visitTractDifferenceBackgrounds[visits[i]] = bgModelBase
575
576 return visitTractDifferenceBackgrounds
577
578 @timeMethod
579 def matchBackgrounds(self, warps, skyMap, refVisitId):
580 """Match science visit's background level to that of reference visit
581
582 Process creates binned images of the full focal plane (in tract
583 coordinates) for all visit IDs, subtracts each from a similarly
584 binned FFP reference image, then generates TractBackground
585 objects.
586
587 The TractBackground objects representing the difference image
588 backgrounds are then used to generate 'offset' images for each warp
589 comprising the full science exposure visit, which are then added to
590 each warp to match the background to that of the reference visit at the
591 warp's location within the tract.
592
593 Best practice uses `psf_matched_warp` images without the
594 detections mask plane set. When using `direct_warp`, sources may bias
595 the difference image background estimation. Mask planes are set in
596 TractBackgroundConfig.
597
598 Fit diagnostics are also calculated and returned.
599
600 Parameters
601 ----------
602 warps : `list`[`~lsst.daf.butler.DeferredDatasetHandle`]
603 List of warped exposures (of type `~lsst.afw.image.ExposureF`).
604 This is ordered by patch ID, then by visit ID
605 skyMap : `lsst.skyMap.SkyMap`
606 SkyMap for deriving the patch/tract dimensions.
607 refVisitId : `int`
608 Chosen reference visit ID to match to.
609
610 Returns
611 -------
612 backgroundInfoList : `list`[`TractBackground`]
613 List of all difference image backgrounds used to match to reference
614 visit warps, in nanojanskies.
615 matchedImageList : `list`[`~lsst.afw.image.ExposureF`]
616 List of all background-matched warps, in nanojanskies.
617 """
618 visits = np.unique([i.dataId["visit"] for i in warps])
619 self.log.info("Processing %d visits", len(visits))
620
621 backgroundInfoList = []
622 matchedImageList = []
623 diffTractBackgrounds = self._makeTractDifferenceBackgrounds(warps, skyMap, refVisitId)
624
625 # Reference visit doesn't need an offset image, so use all 0's
626 im = warps[0].get() # Use arbitrary image as base
627 bkgd = diffTractBackgrounds[refVisitId].toWarpBackground(im)
628 blank = bkgd.getImage()
629 blank *= 0
630
631 for warp in warps:
632 visId = warp.dataId["visit"]
633 if visId == refVisitId:
634 backgroundInfoList.append(bkgd) # Just append a 0 image
635 matchedImageList.append(warp.get())
636 continue
637 self.log.info(
638 "Matching background of %s to same patch in visit %s",
639 warp.dataId,
640 refVisitId,
641 )
642 im = warp.get()
643 maskIm = im.getMaskedImage()
644 tractBg = diffTractBackgrounds[visId]
645 diffModel = tractBg.toWarpBackground(im)
646 bkgdIm = diffModel.getImage()
647 maskIm.image += bkgdIm
648
649 backgroundInfoList.append(diffModel)
650 matchedImageList.append(im)
651
652 return backgroundInfoList, matchedImageList
653
654
655def fitBackground(
656 warp: MaskedImageF, statsCtrl, statsFlag, undersampleStyle
657) -> tuple[BackgroundMI, BackgroundControl]:
658 """Generate a simple binned background masked image for warped or other
659 data
660
661 Parameters
662 ----------
663 warp: `~lsst.afw.image.MaskedImageF`
664 Warped exposure for which to estimate background.
665 statsCtrl : `~lsst.afw.math.StatisticsControl`
666 Statistics control object.
667 statsFlag : `~lsst.afw.math.Property`
668 Statistics control type.
669 undersampleStyle : `str`
670 Behavior if there are too few points in the grid for requested
671 interpolation style.
672
673 Returns
674 -------
675 bkgd: `~lsst.afw.math.BackgroundMI`
676 Background model of masked warp.
677 bgCtrl: `~lsst.afw.math.BackgroundControl`
678 Background control object.
679 """
680 # This only accesses pre-binned images, so no scaling necessary
681 nx = warp.getWidth()
682 ny = warp.getHeight()
683
684 bgCtrl = BackgroundControl(nx, ny, statsCtrl, statsFlag)
685 bgCtrl.setUndersampleStyle(undersampleStyle)
686 bkgd = makeBackground(warp, bgCtrl)
687
688 return bkgd, bgCtrl
Control how to make an approximation.
Definition Approximate.h:48
Pass parameters to a Background object.
Definition Background.h:57
Pass parameters to a Statistics object.
Definition Statistics.h:83