Coverage for python / lsst / drp / tasks / measure_sky_frame_background.py: 0%

42 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-06 08:57 +0000

1# This file is part of drp_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 

22from __future__ import annotations 

23 

24__all__ = () 

25 

26from typing import ClassVar 

27 

28from lsst.afw.geom import makeTransform 

29from lsst.afw.image import ExposureF 

30from lsst.afw.math import WarpingControl, warpImage 

31from lsst.geom import AffineTransform, Box2I, LinearTransform 

32from lsst.meas.algorithms import SubtractBackgroundTask 

33from lsst.pex.config import ConfigurableField 

34from lsst.pipe.base import ( 

35 InputQuantizedConnection, 

36 OutputQuantizedConnection, 

37 PipelineTask, 

38 PipelineTaskConfig, 

39 PipelineTaskConnections, 

40 QuantumContext, 

41 Struct, 

42) 

43from lsst.pipe.base import connectionTypes as cT 

44 

45 

46class MeasureSkyFrameBackgroundConnections( 

47 PipelineTaskConnections, dimensions=["detector", "physical_filter"] 

48): 

49 camera = cT.PrerequisiteInput( 

50 "camera", 

51 storageClass="Camera", 

52 dimensions=["instrument"], 

53 isCalibration=True, 

54 ) 

55 sky_frame = cT.Input( 

56 "sky", 

57 doc="Calibration sky frames.", 

58 storageClass="ExposureF", 

59 dimensions=["detector", "physical_filter"], 

60 isCalibration=True, 

61 ) 

62 sky_frame_background = cT.Output( 

63 "sky_frame_background", 

64 doc="Binned background models fit to the sky frame.", 

65 storageClass="Background", 

66 dimensions=["detector", "physical_filter"], 

67 isCalibration=True, 

68 ) 

69 

70 

71class MeasureSkyFrameBackgroundConfig( 

72 PipelineTaskConfig, pipelineConnections=MeasureSkyFrameBackgroundConnections 

73): 

74 background = ConfigurableField( 

75 target=SubtractBackgroundTask, 

76 doc="Task to perform background subtraction.", 

77 ) 

78 

79 def setDefaults(self): 

80 super().setDefaults() 

81 self.background.statisticsProperty = "MEAN" 

82 self.background.useApprox = False 

83 

84 

85class MeasureSkyFrameBackgroundTask(PipelineTask): 

86 """A task that measures the background on sky frames, effectively binning 

87 them and allowing them to be used as basis functions in 

88 `FitVisitBackgroundTask`. 

89 """ 

90 

91 _DefaultName: ClassVar[str] = "measureSkyFrameBackground" 

92 ConfigClass: ClassVar[type[MeasureSkyFrameBackgroundConfig]] = MeasureSkyFrameBackgroundConfig 

93 config: MeasureSkyFrameBackgroundConfig 

94 

95 def __init__(self, *, config=None, log=None, initInputs=None, **kwargs): 

96 super().__init__(config=config, log=log, initInputs=initInputs, **kwargs) 

97 self.makeSubtask("background") 

98 

99 def runQuantum( 

100 self, 

101 butlerQC: QuantumContext, 

102 inputRefs: InputQuantizedConnection, 

103 outputRefs: OutputQuantizedConnection, 

104 ) -> None: 

105 camera = butlerQC.get(inputRefs.camera) 

106 bbox = camera[butlerQC.quantum.dataId["detector"]].getBBox() 

107 sky_frame = butlerQC.get(inputRefs.sky_frame) 

108 results = self.run(bbox=bbox, sky_frame=sky_frame) 

109 butlerQC.put(results, outputRefs) 

110 

111 def run(self, *, bbox: Box2I, sky_frame: ExposureF) -> Struct: 

112 """Subtract the background from a sky frame. 

113 

114 Parameter 

115 --------- 

116 bbox : `lsst.geom.Box2I` 

117 Bounding box of the full detector the [binned] sky frame 

118 corresponds to. 

119 sky_frame : `lsst.afw.geom.ExposureF` 

120 Sky frame image. Will be subtracted in place. 

121 

122 Returns 

123 ------- 

124 results : `lsst.pipe.base.Struct` 

125 Result struct with a single ``sky_frame_backround`` attribute 

126 (an `lsst.afw.math.BackgroundList`). 

127 """ 

128 # In order to measure the sky frame with the exact same bins used for 

129 # background subtraction in calibrateImage (as will be required by 

130 # fitVisitBackground), we scale the sky frame back up to full size. 

131 # That's pretty silly from an efficiency standpoint, since we're 

132 # ultimately going to bin it back down, but it sidesteps any 

133 # inconsistencies on how to deal with bin sizes that don't evenly 

134 # divide the image, by running the exact same background estimation 

135 # code on images with the exact same dimensions. 

136 linear = LinearTransform.makeScaling(bbox.width / sky_frame.width, bbox.height / sky_frame.height) 

137 transform = makeTransform(AffineTransform(linear)) 

138 full_exposure = ExposureF(bbox) 

139 warpImage(full_exposure.maskedImage, sky_frame.maskedImage, transform, WarpingControl("bilinear")) 

140 full_exposure.maskedImage *= linear.computeDeterminant() 

141 background = self.background.run(full_exposure).background 

142 return Struct(sky_frame_background=background)