Coverage for tests / test_skyCorrection.py: 18%
106 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-21 10:40 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-21 10:40 +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/>.
22import unittest
23from copy import deepcopy
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
34class SkyCorrectionTestCase(lsst.utils.tests.TestCase):
36 def setUp(self):
37 dummyCam = DummyCam()
38 self.camera = dummyCam.getCamera()
39 bbox = self.camera[0].getBBox()
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
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)
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)
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)
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)
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)
125 def tearDown(self):
126 del self.camera
127 del self.calExps
128 del self.calBkgs
129 del self.skyFrames
130 del self.backgroundToPhotometricRatioHandles
132 def testSkyCorrectionDefault(self):
133 """Test SkyCorrectionTask with mostly default configuration values."""
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)
146 def testSkyCorrectionSkyFrameOnly(self):
147 """Test SkyCorrectionTask with the config undoBgModel1 set to True."""
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 )
163 def testSkyCorrectionIlluminationCorrection(self):
164 """Test SkyCorrectionTask with illumination corrections."""
165 config = self.skyCorrectionConfig
166 config.doApplyFlatBackgroundRatio = True
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)
188class MemoryTester(lsst.utils.tests.MemoryTestCase):
189 pass
192def setup_module(module):
193 lsst.utils.tests.init()
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()