Coverage for tests / test_skyCorrection.py: 18%

106 statements  

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

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 

22import unittest 

23from copy import deepcopy 

24 

25import lsst.utils.tests 

26import numpy as np 

27from lsst.afw.image import ExposureF, ImageF 

28from lsst.afw.math import BackgroundMI 

29from lsst.obs.base.instrument_tests import DummyCam 

30from lsst.pipe.base import InMemoryDatasetHandle 

31from lsst.pipe.tasks.skyCorrection import SkyCorrectionConfig, SkyCorrectionTask 

32 

33 

34class SkyCorrectionTestCase(lsst.utils.tests.TestCase): 

35 

36 def setUp(self): 

37 dummyCam = DummyCam() 

38 self.camera = dummyCam.getCamera() 

39 bbox = self.camera[0].getBBox() 

40 

41 # Configs below set to approximate HSC defaults 

42 self.skyCorrectionConfig = SkyCorrectionConfig() 

43 self.skyCorrectionConfig.doMaskObjects = False 

44 # Set bgModel1 size to a single bin for the whole plane (aka constant) 

45 self.skyCorrectionConfig.bgModel1.xSize = 8192 * 0.015 

46 self.skyCorrectionConfig.bgModel1.ySize = 8192 * 0.015 

47 self.skyCorrectionConfig.bgModel1.pixelSize = 0.015 

48 self.skyCorrectionConfig.bgModel2.xSize = 256 * 0.015 

49 self.skyCorrectionConfig.bgModel2.ySize = 256 * 0.015 

50 self.skyCorrectionConfig.bgModel2.pixelSize = 0.015 

51 

52 # Generate calexp/calexpBackground/sky for all detectors 

53 self.calExps = [] 

54 self.calBkgs = [] 

55 self.backgroundToPhotometricRatioHandles = [] 

56 self.skyFrames = [] 

57 self.background_level = 3000 

58 self.sky_level = 5 

59 self.background_to_photometric_ratio_level = 1.1 

60 for detector in [0, 1]: 

61 rng = np.random.default_rng(detector) 

62 

63 # Science image 

64 calexp = ExposureF(bbox) 

65 calexp.maskedImage.set(0.0, 0x0, 650.0) 

66 calexp.setDetector(self.camera[detector]) 

67 # Add a sky frame signature to a subregion of the image 

68 sky_frame_bin_size = 32 

69 x_start = 32 * sky_frame_bin_size 

70 x_stop = 64 * sky_frame_bin_size 

71 y_start = 31 * sky_frame_bin_size 

72 y_stop = 63 * sky_frame_bin_size 

73 calexp.image.array[:, x_start:x_stop] += self.sky_level 

74 calexp.image.array[y_start:y_stop, :] += self.sky_level 

75 # Add random noise 

76 calexp.image.array += rng.normal(0.0, 25.0, (bbox.getDimensions().y, bbox.getDimensions().x)) 

77 self.calExps.append(calexp) 

78 

79 # Background image 

80 backgroundImage = ExposureF(bbox) 

81 backgroundImage.maskedImage.set(0.0, 0x0, 1.0) 

82 backgroundImage.setDetector(self.camera[detector]) 

83 backgroundImage.image.array += self.background_level 

84 background = BackgroundMI(bbox, backgroundImage.getMaskedImage()) 

85 calexpBackground = lsst.afw.math.BackgroundList( 

86 ( 

87 background, 

88 lsst.afw.math.Interpolate.CONSTANT, 

89 lsst.afw.math.UndersampleStyle.REDUCE_INTERP_ORDER, 

90 lsst.afw.math.ApproximateControl.UNKNOWN, 

91 0, 

92 0, 

93 False, 

94 ) 

95 ) 

96 self.calBkgs.append(calexpBackground) 

97 

98 # Sky frame 

99 sky = ExposureF(128, 125) 

100 sky.maskedImage.set(0.0, 0x0, 1.0) 

101 sky.setDetector(self.camera[detector]) 

102 header = sky.getMetadata() 

103 header.set("BOX.MINX", bbox.getMinX()) 

104 header.set("BOX.MINY", bbox.getMinY()) 

105 header.set("BOX.MAXX", bbox.getMaxX()) 

106 header.set("BOX.MAXY", bbox.getMaxY()) 

107 header.set("ALGORITHM", "NATURAL_SPLINE") 

108 sky.image.array[:, 32:64] += 1 # x 

109 sky.image.array[31:63, :] += 1 # y 

110 # Add random noise 

111 sky.image.array += rng.normal(0.0, 0.1, (125, 128)) 

112 sky.image.array -= np.sum(sky.image.array) / (125 * 128) 

113 self.skyFrames.append(sky) 

114 

115 # Illumination correction handles. 

116 backgroundToPhotometricRatio = ImageF(bbox) 

117 backgroundToPhotometricRatio.array[:, :] = self.background_to_photometric_ratio_level 

118 backgroundToPhotometricRatioHandle = InMemoryDatasetHandle( 

119 backgroundToPhotometricRatio, 

120 detector=detector, 

121 visit=0, 

122 ) 

123 self.backgroundToPhotometricRatioHandles.append(backgroundToPhotometricRatioHandle) 

124 

125 def tearDown(self): 

126 del self.camera 

127 del self.calExps 

128 del self.calBkgs 

129 del self.skyFrames 

130 del self.backgroundToPhotometricRatioHandles 

131 

132 def testSkyCorrectionDefault(self): 

133 """Test SkyCorrectionTask with mostly default configuration values.""" 

134 

135 skyCorrectionTask = SkyCorrectionTask(config=self.skyCorrectionConfig) 

136 # Pass in deep copies, as the task modifies the input data 

137 results = skyCorrectionTask.run( 

138 deepcopy(self.calExps), deepcopy(self.calBkgs), self.skyFrames, self.camera 

139 ) 

140 skyFrameScale = results.skyFrameScale 

141 skyCorr = results.skyCorr 

142 self.assertEqual(len(skyCorr), len(self.calExps)) 

143 self.assertAlmostEqual(skyFrameScale, self.sky_level, delta=1e-1) 

144 self.assertAlmostEqual(np.nanmean(results.calExpMosaic.array), 0, delta=1e-2) 

145 

146 def testSkyCorrectionSkyFrameOnly(self): 

147 """Test SkyCorrectionTask with the config undoBgModel1 set to True.""" 

148 

149 skyCorrectionConfig = deepcopy(self.skyCorrectionConfig) 

150 skyCorrectionConfig.undoBgModel1 = True 

151 skyCorrectionConfig.doBgModel2 = False 

152 skyCorrectionTask = SkyCorrectionTask(config=skyCorrectionConfig) 

153 # Pass in deep copies, as the task modifies the input data 

154 results = skyCorrectionTask.run( 

155 deepcopy(self.calExps), deepcopy(self.calBkgs), self.skyFrames, self.camera 

156 ) 

157 self.assertAlmostEqual( 

158 np.nanmean(results.calExpMosaic.array), 

159 np.nanmean(self.calExps[0].image.array) + self.background_level, 

160 delta=1e-2, 

161 ) 

162 

163 def testSkyCorrectionIlluminationCorrection(self): 

164 """Test SkyCorrectionTask with illumination corrections.""" 

165 config = self.skyCorrectionConfig 

166 config.doApplyFlatBackgroundRatio = True 

167 

168 skyCorrectionTask = SkyCorrectionTask(config=config) 

169 # Pass in deep copies, as the task modifies the input data 

170 results = skyCorrectionTask.run( 

171 deepcopy(self.calExps), 

172 deepcopy(self.calBkgs), 

173 self.skyFrames, 

174 self.camera, 

175 backgroundToPhotometricRatioHandles=self.backgroundToPhotometricRatioHandles, 

176 ) 

177 skyFrameScale = results.skyFrameScale 

178 skyCorr = results.skyCorr 

179 self.assertEqual(len(skyCorr), len(self.calExps)) 

180 self.assertAlmostEqual( 

181 skyFrameScale, 

182 self.sky_level * self.background_to_photometric_ratio_level, 

183 delta=1e-1, 

184 ) 

185 self.assertAlmostEqual(np.nanmean(results.calExpMosaic.array), 0, delta=1e-2) 

186 

187 

188class MemoryTester(lsst.utils.tests.MemoryTestCase): 

189 pass 

190 

191 

192def setup_module(module): 

193 lsst.utils.tests.init() 

194 

195 

196if __name__ == "__main__": 196 ↛ 197line 196 didn't jump to line 197 because the condition on line 196 was never true

197 lsst.utils.tests.init() 

198 unittest.main()