Coverage for tests/test_assemble_coadd.py: 29%
153 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-02 02:58 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-02 02:58 -0700
1# This file is part of drp_tasks.
2#
3# LSST Data Management System
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6# See COPYRIGHT file at the top of the source tree.
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22"""Test AssembleCoaddTask and its variants.
23"""
24import unittest
26import lsst.pipe.base as pipeBase
27import lsst.utils.tests
28import numpy as np
29from assemble_coadd_test_utils import MockCoaddTestData, makeMockSkyInfo
30from lsst.drp.tasks.assemble_coadd import (
31 AssembleCoaddConfig,
32 AssembleCoaddTask,
33 CompareWarpAssembleCoaddConfig,
34 CompareWarpAssembleCoaddTask,
35)
36from lsst.drp.tasks.dcr_assemble_coadd import DcrAssembleCoaddConfig, DcrAssembleCoaddTask
38__all__ = [
39 "MockAssembleCoaddConfig",
40 "MockAssembleCoaddTask",
41 "MockCompareWarpAssembleCoaddConfig",
42 "MockCompareWarpAssembleCoaddTask",
43]
46class MockAssembleCoaddConfig(AssembleCoaddConfig):
47 def setDefaults(self):
48 super().setDefaults()
49 self.doWrite = False
52class MockAssembleCoaddTask(AssembleCoaddTask):
53 """Lightly modified version of `AssembleCoaddTask` for use with unit tests.
55 The modifications bypass the usual middleware for loading data and setting
56 up the Task, and instead supply in-memory mock data references to the `run`
57 method so that the coaddition algorithms can be tested without a Butler.
58 """
60 ConfigClass = MockAssembleCoaddConfig
62 def __init__(self, **kwargs):
63 super().__init__(**kwargs)
64 self.warpType = self.config.warpType
65 self.makeSubtask("interpImage")
66 self.makeSubtask("scaleZeroPoint")
68 def processResults(self, *args, **kwargs):
69 "This should be tested separately."
70 pass
72 def runQuantum(self, mockSkyInfo, warpRefList, *args):
73 """Modified interface for testing coaddition algorithms without a
74 Butler.
76 Parameters
77 ----------
78 mockSkyInfo : `lsst.pipe.base.Struct`
79 A simple container that supplies a bounding box and WCS in the
80 same format as the output of
81 `lsst.pipe.tasks.CoaddBaseTask.getSkyInfo`
82 warpRefList : `list` of `lsst.pipe.tasks.MockExposureReference`
83 Data references to the test exposures that will be coadded,
84 using the Gen 3 API.
86 Returns
87 -------
88 retStruct : `lsst.pipe.base.Struct`
89 The coadded exposure and associated metadata.
90 """
91 inputs = self.prepareInputs(warpRefList)
93 retStruct = self.run(
94 mockSkyInfo,
95 inputs.tempExpRefList,
96 inputs.imageScalerList,
97 inputs.weightList,
98 supplementaryData=pipeBase.Struct(),
99 )
100 return retStruct
103class MockCompareWarpAssembleCoaddConfig(CompareWarpAssembleCoaddConfig):
104 def setDefaults(self):
105 super().setDefaults()
106 self.assembleStaticSkyModel.retarget(MockAssembleCoaddTask)
107 self.assembleStaticSkyModel.doWrite = False
108 self.doWrite = False
111class MockCompareWarpAssembleCoaddTask(MockAssembleCoaddTask, CompareWarpAssembleCoaddTask):
112 """Lightly modified version of `CompareWarpAssembleCoaddTask`
113 for use with unit tests.
115 The modifications bypass the usual middleware for loading data and setting
116 up the Task, and instead supply in-memory mock data references to the `run`
117 method so that the coaddition algorithms can be tested without a Butler.
118 """
120 ConfigClass = MockCompareWarpAssembleCoaddConfig
121 _DefaultName = "compareWarpAssembleCoadd"
123 def __init__(self, *args, **kwargs):
124 CompareWarpAssembleCoaddTask.__init__(self, *args, **kwargs)
126 def runQuantum(self, mockSkyInfo, warpRefList, *args):
127 inputs = self.prepareInputs(warpRefList)
129 assembleStaticSkyModel = MockAssembleCoaddTask(config=self.config.assembleStaticSkyModel)
130 templateCoadd = assembleStaticSkyModel.runQuantum(mockSkyInfo, warpRefList)
132 supplementaryData = pipeBase.Struct(
133 templateCoadd=templateCoadd.coaddExposure,
134 nImage=templateCoadd.nImage,
135 warpRefList=templateCoadd.warpRefList,
136 imageScalerList=templateCoadd.imageScalerList,
137 weightList=templateCoadd.weightList,
138 )
140 retStruct = self.run(
141 mockSkyInfo,
142 inputs.tempExpRefList,
143 inputs.imageScalerList,
144 inputs.weightList,
145 supplementaryData=supplementaryData,
146 )
147 return retStruct
150class MockDcrAssembleCoaddConfig(DcrAssembleCoaddConfig):
151 def setDefaults(self):
152 super().setDefaults()
153 self.assembleStaticSkyModel.retarget(MockCompareWarpAssembleCoaddTask)
154 self.assembleStaticSkyModel.doWrite = False
155 self.doWrite = False
156 self.effectiveWavelength = 476.31 # Use LSST g band values for the test.
157 self.bandwidth = 552.0 - 405.0
160class MockDcrAssembleCoaddTask(MockCompareWarpAssembleCoaddTask, DcrAssembleCoaddTask):
161 """Lightly modified version of `DcrAssembleCoaddTask`
162 for use with unit tests.
164 The modifications bypass the usual middleware for loading data and setting
165 up the Task, and instead supply in-memory mock data references to the `run`
166 method so that the coaddition algorithms can be tested without a Butler.
167 """
169 ConfigClass = MockDcrAssembleCoaddConfig
170 _DefaultName = "dcrAssembleCoadd"
172 def __init__(self, *args, **kwargs):
173 DcrAssembleCoaddTask.__init__(self, *args, **kwargs)
176class MockInputMapAssembleCoaddConfig(MockCompareWarpAssembleCoaddConfig):
177 def setDefaults(self):
178 super().setDefaults()
179 self.doInputMap = True
182class MockInputMapAssembleCoaddTask(MockCompareWarpAssembleCoaddTask):
183 """Lightly modified version of `CompareWarpAssembleCoaddTask`
184 for use with unit tests.
186 The modifications bypass the usual middleware for loading data and setting
187 up the Task, and instead supply in-memory mock data references to the `run`
188 method so that the coaddition algorithms can be tested without a Butler.
189 """
191 ConfigClass = MockInputMapAssembleCoaddConfig
192 _DefaultName = "inputMapAssembleCoadd"
194 def __init__(self, *args, **kwargs):
195 CompareWarpAssembleCoaddTask.__init__(self, *args, **kwargs)
198class AssembleCoaddTestCase(lsst.utils.tests.TestCase):
199 """Tests of AssembleCoaddTask and its derived classes.
201 These tests bypass the middleware used for accessing data and managing Task
202 execution.
203 """
205 def setUp(self):
206 patch = 42
207 tract = 0
208 testData = MockCoaddTestData(fluxRange=1e4)
209 exposures = {}
210 matchedExposures = {}
211 for expId in range(100, 110):
212 exposures[expId], matchedExposures[expId] = testData.makeTestImage(expId)
213 self.dataRefList = testData.makeDataRefList(
214 exposures, matchedExposures, "direct", patch=patch, tract=tract
215 )
216 self.dataRefListPsfMatched = testData.makeDataRefList(
217 exposures, matchedExposures, "psfMatched", patch=patch, tract=tract
218 )
219 self.skyInfo = makeMockSkyInfo(testData.bbox, testData.wcs, patch=patch)
221 def checkRun(self, assembleTask, warpType="direct"):
222 """Check that the task runs successfully."""
223 dataRefList = self.dataRefListPsfMatched if warpType == "psfMatched" else self.dataRefList
224 result = assembleTask.runQuantum(self.skyInfo, dataRefList)
226 # Check that we produced an exposure.
227 self.assertTrue(result.coaddExposure is not None)
229 def testAssembleBasic(self):
230 config = MockAssembleCoaddConfig()
231 assembleTask = MockAssembleCoaddTask(config=config)
232 self.checkRun(assembleTask)
234 def testAssemblePsfMatched(self):
235 config = MockAssembleCoaddConfig(warpType="psfMatched")
236 assembleTask = MockAssembleCoaddTask(config=config)
237 self.checkRun(assembleTask, warpType="psfMatched")
239 def testAssembleCompareWarp(self):
240 config = MockCompareWarpAssembleCoaddConfig()
241 assembleTask = MockCompareWarpAssembleCoaddTask(config=config)
242 self.checkRun(assembleTask)
244 def testAssembleDCR(self):
245 config = MockDcrAssembleCoaddConfig()
246 assembleTask = MockDcrAssembleCoaddTask(config=config)
247 self.checkRun(assembleTask)
249 def testOnlineCoadd(self):
250 config = MockInputMapAssembleCoaddConfig()
251 config.statistic = "MEAN"
252 assembleTask = MockInputMapAssembleCoaddTask(config=config)
254 dataRefList = self.dataRefList
255 results = assembleTask.runQuantum(self.skyInfo, dataRefList)
256 coadd = results.coaddExposure
258 configOnline = MockInputMapAssembleCoaddConfig()
259 configOnline.statistic = "MEAN"
260 configOnline.doOnlineForMean = True
261 configOnline.validate()
262 assembleTaskOnline = MockInputMapAssembleCoaddTask(config=configOnline)
264 resultsOnline = assembleTaskOnline.runQuantum(self.skyInfo, dataRefList)
265 coaddOnline = resultsOnline.coaddExposure
267 self.assertFloatsAlmostEqual(coaddOnline.image.array, coadd.image.array, rtol=1e-3)
268 self.assertFloatsAlmostEqual(coaddOnline.variance.array, coadd.variance.array, rtol=1e-6)
269 self.assertMasksEqual(coaddOnline.mask, coadd.mask)
271 def testInputMap(self):
272 config = MockInputMapAssembleCoaddConfig()
273 assembleTask = MockInputMapAssembleCoaddTask(config=config)
275 # Make exposures where one of them has a bad region.
276 patch = 42
277 tract = 0
278 testData = MockCoaddTestData(fluxRange=1e4)
279 exposures = {}
280 matchedExposures = {}
281 for expId in range(100, 110):
282 if expId == 105:
283 badBox = lsst.geom.Box2I(
284 lsst.geom.Point2I(testData.bbox.beginX + 10, testData.bbox.beginY + 10),
285 lsst.geom.Extent2I(100, 100),
286 )
287 else:
288 badBox = None
289 exposures[expId], matchedExposures[expId] = testData.makeTestImage(expId, badRegionBox=badBox)
290 dataRefList = testData.makeDataRefList(
291 exposures, matchedExposures, "direct", patch=patch, tract=tract
292 )
294 results = assembleTask.runQuantum(self.skyInfo, dataRefList)
296 inputMap = results.inputMap
297 validPix, raPix, decPix = inputMap.valid_pixels_pos(return_pixels=True)
299 # Confirm that all the map pixels are in the bounding box
300 # Exposure 100 is the first one and they all have the same WCS in the
301 # tests.
302 xPix, yPix = exposures[100].getWcs().skyToPixelArray(raPix, decPix, degrees=True)
303 self.assertGreater(xPix.min(), testData.bbox.beginX)
304 self.assertGreater(yPix.min(), testData.bbox.beginY)
305 self.assertLess(xPix.max(), testData.bbox.endX)
306 self.assertLess(xPix.max(), testData.bbox.endY)
308 # Confirm that all exposures except 105 are completely covered
309 # This assumes we have one input per visit in the mock data.
310 metadata = inputMap.metadata
311 visitBitDict = {}
312 for bit in range(inputMap.wide_mask_maxbits):
313 if f"B{bit:04d}VIS" in metadata:
314 visitBitDict[metadata[f"B{bit:04d}VIS"]] = bit
315 for expId in range(100, 110):
316 if expId == 105:
317 self.assertFalse(np.all(inputMap.check_bits_pix(validPix, [visitBitDict[expId]])))
318 else:
319 self.assertTrue(np.all(inputMap.check_bits_pix(validPix, [visitBitDict[expId]])))
322class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
323 pass
326def setup_module(module):
327 lsst.utils.tests.init()
330if __name__ == "__main__": 330 ↛ 331line 330 didn't jump to line 331, because the condition on line 330 was never true
331 lsst.utils.tests.init()
332 unittest.main()