Coverage for tests/test_coaddInputs.py: 26%
131 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-01 02:33 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-01 02:33 -0700
1#
2# LSST Data Management System
3# Copyright 2008-2016 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
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 <http://www.lsstcorp.org/LegalNotices/>.
21#
22"""
23Tests for lsst.afw.table.CoaddInputs
25Note: generating the version 1 data requires some complicated machinations
26because CoaddInputRecorderTask had bugs:
27- Setup pipe_tasks 12.1, or a similar version old enough to have version 1 CoaddInput/ExposureTable
28- Edit this file as follows:
29 - Set SaveCoadd = True
30 - Set self.version to 2 in CoaddInputsTestCase
31- Edit CoaddInputRecorderTask as follows:
32 - Apply all fixes to CoaddInputRecorderTask from DM-7976
33 - Comment out the line that saves VisitInfo (since version 1 doesn't have it)
34- Edit ups/pipe_tasks.table as follows:
35 - Do not import obs_base (since it did not exist at that time)
36- "setup -r . 'j" to setup this version of pipe_tasks
37- "python tests/testCoaddInputs.py" to run this test.
38 You will see some errors, but the needed data file will be written:
39 tests/data/testCoaddInputs_coadd_with_version_1_data.fits
40- Save the data file to the repository (but do not save any other changes).
41"""
42import os.path
43import unittest
45import numpy as np
47import lsst.utils.tests
48import lsst.pex.exceptions
49from lsst.daf.base import DateTime
50import lsst.afw.cameraGeom.testUtils
51from lsst.afw.coord import Observatory, Weather
52import lsst.geom
53import lsst.afw.geom
54import lsst.afw.image
55from lsst.afw.detection import GaussianPsf
56from lsst.afw.math import ChebyshevBoundedField
57from lsst.pipe.tasks.coaddInputRecorder import CoaddInputRecorderTask
59SaveCoadd = False # if True then save coadd even if test passes (always saved if a test fails)
62class MockExposure:
64 """Factory to make simple mock exposures suitable to put in a coadd
66 The metadata is set, but not the pixels
67 """
69 def __init__(self, numExp, coaddInputRecorder, version=2):
70 """Make a MockExposure
72 @param[in] numExp total number of exposures that will be made
73 @param[in] coaddInputRecoder an instance of CoaddInputRecorderTask
74 @param[in] version desired version of CoaddInput/ExposureTable;
75 1 for no VisitInfo; this will only produce the desired result
76 if you run the test with code from before VisitInfo, e.g. version 12.1)
77 2 to include VisitInfo
78 """
79 self.numExp = int(numExp)
80 self.coaddInputRecorder = coaddInputRecorder
81 self.version = int(version)
83 def makeExposure(self, universalId):
84 """Make a tiny exposure with exposure info set, but no pixels
86 In particular, exposure info is set as a record in a table, so it can be recorded in a coadd
87 """
88 inputRecorder = self.coaddInputRecorder.makeCoaddTempExpRecorder(universalId, self.numExp)
89 bbox = lsst.geom.Box2I(lsst.geom.Point2I(100, 100), lsst.geom.Extent2I(10, 10))
91 detectorName = "detector {}".format(universalId)
92 detector = lsst.afw.cameraGeom.testUtils.DetectorWrapper(name=detectorName, id=universalId).detector
94 exp = lsst.afw.image.ExposureF(bbox)
95 exp.setDetector(detector)
97 expInfo = exp.getInfo()
98 expInfo.id = 10313423
99 scale = 5.1e-5*lsst.geom.degrees
100 cdMatrix = lsst.afw.geom.makeCdMatrix(scale=scale)
101 wcs = lsst.afw.geom.makeSkyWcs(
102 crpix=lsst.geom.Point2D(5, 5),
103 crval=lsst.geom.SpherePoint(10, 45, lsst.geom.degrees),
104 cdMatrix=cdMatrix,
105 )
106 expInfo.setWcs(wcs)
107 expInfo.setPsf(GaussianPsf(5, 5, 2.5))
108 expInfo.setPhotoCalib(lsst.afw.image.makePhotoCalibFromCalibZeroPoint(1.1e12, 2.2e10))
109 expInfo.setApCorrMap(self.makeApCorrMap())
110 expInfo.setValidPolygon(lsst.afw.geom.Polygon(lsst.geom.Box2D(bbox).getCorners()))
111 expInfo.setFilter(lsst.afw.image.FilterLabel(physical="fakeFilter", band="fake"))
112 if self.version > 1:
113 expInfo.setVisitInfo(self.makeVisitInfo())
115 inputRecorder.addCalExp(calExp=exp, ccdId=universalId, nGoodPix=100)
116 inputRecorder.finish(coaddTempExp=exp, nGoodPix=100)
118 return exp
120 @staticmethod
121 def makeWcs():
122 scale = 5.1e-5*lsst.geom.degrees
123 cdMatrix = lsst.afw.geom.makeCdMatrix(scale=scale)
124 return lsst.afw.geom.makeSkyWcs(
125 crpix=lsst.geom.Point2D(5, 5),
126 crval=lsst.geom.SpherePoint(10, 45, lsst.geom.degrees),
127 cdMatrix=cdMatrix,
128 )
130 @staticmethod
131 def makeVisitInfo():
132 return lsst.afw.image.VisitInfo(
133 10313423,
134 10.01,
135 11.02,
136 DateTime(65321.1, DateTime.MJD, DateTime.TAI),
137 12345.1,
138 45.1*lsst.geom.degrees,
139 lsst.geom.SpherePoint(23.1, 73.2, lsst.geom.degrees),
140 lsst.geom.SpherePoint(134.5, 33.3, lsst.geom.degrees),
141 1.73,
142 73.2*lsst.geom.degrees,
143 lsst.afw.image.RotType.SKY,
144 Observatory(11.1*lsst.geom.degrees, 22.2*lsst.geom.degrees, 0.333),
145 Weather(1.1, 2.2, 34.5),
146 )
148 @staticmethod
149 def makeApCorrMap():
150 """Make a trivial ApCorrMap with three elements"""
151 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-5, -5), lsst.geom.Point2I(5, 5))
152 apCorrMap = lsst.afw.image.ApCorrMap()
153 for name in ("a", "b", "c"):
154 apCorrMap.set(name, ChebyshevBoundedField(bbox, np.zeros((3, 3), dtype=float)))
155 return apCorrMap
158class MockCoadd:
160 """Class to make a mock coadd
161 """
163 def __init__(self, numExp, version=2):
164 """Create a coadd with the specified number of exposures.
166 @param[in] numExp total number of exposures that will be made
167 @param[in] version desired version of CoaddInput/ExposureTable; see MockExposure for details
169 Useful fields include:
170 - coadd
171 - exposures a list of exposures that went into the coadd
172 """
173 coaddInputRecorder = CoaddInputRecorderTask(name="coaddInputRecorder")
174 mockExposure = MockExposure(numExp=numExp, coaddInputRecorder=coaddInputRecorder, version=version)
175 self.exposures = [mockExposure.makeExposure(i) for i in range(numExp)]
177 exp0 = self.exposures[0]
178 self.coadd = lsst.afw.image.ExposureF(exp0.getBBox())
179 self.coadd.setWcs(exp0.getWcs())
181 coaddInputs = coaddInputRecorder.makeCoaddInputs()
182 for exp in self.exposures:
183 coaddInputRecorder.addVisitToCoadd(coaddInputs=coaddInputs, coaddTempExp=exp, weight=1.0/numExp)
184 self.coadd.getInfo().setCoaddInputs(coaddInputs)
187class CoaddInputsTestCase(lsst.utils.tests.TestCase):
189 def setUp(self):
190 self.version = 2 # desired version of ExposureTable/CoaddInput
191 self.numExp = 3
192 mockCoadd = MockCoadd(numExp=self.numExp, version=self.version)
193 self.coadd = mockCoadd.coadd
194 self.exposures = mockCoadd.exposures
195 self.dataDir = os.path.join(os.path.dirname(__file__), "data")
197 def tearDown(self):
198 del self.coadd
199 del self.exposures
201 def assertPsfsAlmostEqual(self, psf1, psf2):
202 im1 = psf1.computeImage(psf1.getAveragePosition())
203 im2 = psf2.computeImage(psf2.getAveragePosition())
204 self.assertImagesAlmostEqual(im1, im2)
206 def getCoaddPath(self, version):
207 return os.path.join(self.dataDir,
208 "testCoaddInputs_coadd_with_version_{}_data.fits".format(version))
210 def testPersistence(self):
211 """Read and write a coadd and check the CoaddInputs"""
212 coaddPath = self.getCoaddPath(version=self.version)
213 self.coadd.writeFits(coaddPath)
214 coadd = lsst.afw.image.ExposureF(coaddPath)
215 coaddInputs = coadd.getInfo().getCoaddInputs()
216 self.assertCoaddInputsOk(coaddInputs, version=self.version)
217 if not SaveCoadd:
218 os.unlink(coaddPath)
219 else:
220 print("SaveCoadd true; saved coadd as: %r" % (coaddPath,))
222 def testReadV1Coadd(self):
223 """Read a coadd that contains version 1 CoaddInputs
225 The test code in question has FK5 WCS
226 """
227 coaddPath = self.getCoaddPath(version=1)
228 print("coaddPath=", coaddPath)
229 coadd = lsst.afw.image.ExposureF(coaddPath)
230 coaddWcs = coadd.getWcs()
231 # the exposure in question uses FK5 for its WCS so update the exposures
232 for exposure in self.exposures:
233 exposure.setWcs(coaddWcs)
234 coaddInputs = coadd.getInfo().getCoaddInputs()
235 self.assertCoaddInputsOk(coaddInputs, version=1)
237 def assertCoaddInputsOk(self, coaddInputs, version):
238 self.assertIsNotNone(coaddInputs)
239 for expTable in (coaddInputs.ccds, coaddInputs.visits):
240 self.assertEqual(len(expTable), 3)
241 for i, expRec in enumerate(expTable):
242 exp = self.exposures[i]
243 expInfo = exp.getInfo()
244 self.assertEqual(expRec.getId(), i)
245 self.assertEqual(expRec.getBBox(), exp.getBBox())
246 self.assertWcsAlmostEqualOverBBox(expRec.getWcs(), expInfo.getWcs(), expRec.getBBox())
247 self.assertPsfsAlmostEqual(expRec.getPsf(), exp.getPsf())
248 self.assertEqual(expRec.getPhotoCalib(), expInfo.getPhotoCalib())
249 self.assertEqual(len(expRec.getApCorrMap()), 3)
250 self.assertEqual(set(expRec.getApCorrMap().keys()), set(expInfo.getApCorrMap().keys()))
251 self.assertFloatsAlmostEqual(np.array(expRec.getValidPolygon().getVertices()),
252 np.array(expInfo.getValidPolygon().getVertices()))
253 if version > 1:
254 self.assertEqual(expRec.getVisitInfo(), expInfo.getVisitInfo())
255 else:
256 self.assertIsNone(expRec.getVisitInfo())
259class MemoryTester(lsst.utils.tests.MemoryTestCase):
260 pass
263def setup_module(module):
264 lsst.utils.tests.init()
267if __name__ == "__main__": 267 ↛ 268line 267 didn't jump to line 268, because the condition on line 267 was never true
268 lsst.utils.tests.init()
269 unittest.main()