Coverage for tests/test_transmissionCurve.py: 13%
228 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-20 02:20 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-20 02:20 -0700
1#
2# LSST Data Management System
3# Copyright 2017 LSST/AURA.
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#
23import unittest
25import numpy as np
27import lsst.utils.tests
28import lsst.pex.exceptions
29import lsst.geom
30import lsst.afw.geom
31import lsst.afw.image
32import lsst.afw.table
35def makeTestCurve(random, x0, x1, y0=0.0, y1=0.0):
36 """Return a piecewise callable appropriate for testing
37 TransmissionCurve objects.
39 The returned callable can be called with either scalar or numpy.ndarray
40 arguments.
42 Between x0 and x1, the returned function is a nonnegative 4th-order
43 polynomial. At and below x0 it is constant with value y0, and at and
44 above x1 it is equal to y1. At exactly x0 and x1 its first derivative
45 is zero.
47 y0 and y1 may be None to indicate a random value should be drawn
48 (on the interval [0, 1]).
49 """
50 if y0 is None:
51 y0 = random.rand()
52 if y1 is None:
53 y1 = random.rand()
54 assert y0 >= 0.0
55 assert y1 >= 0.0
56 alpha0 = np.abs(y0 - y1)
57 if alpha0 == 0.0:
58 alpha0 = 1.0
59 mu = (x1 - x0)*(0.25 + 0.5*random.rand())
60 alpha = alpha0*(0.25 + 0.5*random.rand())
61 n = 5
62 A = np.zeros([n, n], dtype=float)
63 dx = x1 - x0
64 A[0, 0] = 1.0
65 A[1, :] = [dx**k for k in range(n)]
66 A[2, 1] = 1.0
67 A[3, :] = [k*dx**(k - 1) for k in range(n)]
68 A[4, :] = [mu**k for k in range(n)]
69 b = np.array([y0, y1, 0.0, 0.0, alpha], dtype=float)
70 coeffs = np.linalg.solve(A, b)
72 def curve(x):
73 result = sum(c*(x - x0)**k for k, c in enumerate(coeffs))
74 result[x <= x0] = y0
75 result[x >= x1] = y1
76 return result
78 return curve
81class TransmissionCurveTestCase(lsst.utils.tests.TestCase):
83 def setUp(self):
84 self.random = np.random.RandomState(1)
85 self.points = [lsst.geom.Point2D(self.random.rand(), self.random.rand()) for i in range(5)]
86 self.minWavelength = 5000 + self.random.rand()
87 self.maxWavelength = 5500 + self.random.rand()
89 def randIfNone(self, v):
90 """Return a random number if the given input is None, but pass it through if it is not."""
91 if v is None:
92 return self.random.rand()
93 return v
95 def checkEvaluation(self, tc, wavelengths, expected, rtol=0.0, atol=0.0):
96 """Test that evaluating a TransmissionCurve on the given wavelengths array yields the given
97 expected throughputs.
98 """
99 for point in self.points:
100 throughput = tc.sampleAt(point, wavelengths)
101 self.assertFloatsAlmostEqual(throughput, expected, rtol=rtol, atol=atol)
102 throughput2 = np.zeros(wavelengths.size, dtype=float)
103 tc.sampleAt(point, wavelengths, out=throughput2)
104 self.assertFloatsEqual(throughput2, throughput)
106 def assertTransmissionCurvesEqual(self, a, b, rtol=0.0, atol=0.0):
107 """Test whether two TransimssionCurves are equivalent."""
108 self.assertEqual(a.getWavelengthBounds(), b.getWavelengthBounds())
109 self.assertEqual(a.getThroughputAtBounds(), b.getThroughputAtBounds())
110 wavelengths = np.linspace(*(a.getWavelengthBounds() + (100,)))
111 for point in self.points:
112 self.assertFloatsAlmostEqual(
113 a.sampleAt(point, wavelengths),
114 b.sampleAt(point, wavelengths),
115 rtol=rtol, atol=atol
116 )
118 def checkPersistence(self, tc, points=None):
119 """Test that a TransmissionCurve round-trips through persistence."""
120 if points is None:
121 points = self.points
122 with lsst.utils.tests.getTempFilePath(".fits") as filename:
123 tc.writeFits(filename)
124 tc2 = lsst.afw.image.TransmissionCurve.readFits(filename)
125 self.assertTransmissionCurvesEqual(tc, tc2)
127 def makeAndCheckSpatiallyConstant(self, curve, wavelengths, throughputAtMin, throughputAtMax):
128 """Construct a constant TransmissionCurve and apply basic tests to it."""
129 throughput = curve(wavelengths)
130 tc = lsst.afw.image.TransmissionCurve.makeSpatiallyConstant(throughput, wavelengths,
131 throughputAtMin, throughputAtMax)
132 self.assertEqual(tc.getWavelengthBounds(), (wavelengths[0], wavelengths[-1]))
133 self.assertEqual(tc.getThroughputAtBounds(), (throughputAtMin, throughputAtMax))
134 self.checkEvaluation(tc, wavelengths, throughput)
135 self.checkEvaluation(tc, np.array([2*wavelengths[0] - wavelengths[1]]), throughputAtMin)
136 self.checkEvaluation(tc, np.array([2*wavelengths[-1] - wavelengths[-2]]), throughputAtMax)
137 # Check that we get decent results when interpolating to different wavelengths.
138 wavelengths1 = np.linspace(wavelengths[0] - 10, wavelengths[-1] + 10, 200)
139 self.checkEvaluation(tc, wavelengths1, curve(wavelengths1), rtol=1E-2, atol=1E-2)
140 # Test that multiplication with identity is a no-op
141 tc2 = tc * lsst.afw.image.TransmissionCurve.makeIdentity()
142 self.assertTransmissionCurvesEqual(tc, tc2)
143 return tc
145 def checkSpatiallyConstantEvenSpacing(self, throughputAtMin, throughputAtMax):
146 """Test that we can construct and use a spatially-constant
147 TransmissionCurve initialized with an evenly-spaced wavelength
148 array.
149 """
150 throughputAtMin = self.randIfNone(throughputAtMin)
151 throughputAtMax = self.randIfNone(throughputAtMax)
152 wavelengths = np.linspace(self.minWavelength, self.maxWavelength, 100)
153 curve = makeTestCurve(self.random, self.minWavelength, self.maxWavelength,
154 throughputAtMin, throughputAtMax)
155 tc = self.makeAndCheckSpatiallyConstant(curve, wavelengths, throughputAtMin, throughputAtMax)
156 self.checkPersistence(tc)
158 def testSpatiallyConstantEvenSpacing(self):
159 """Invoke all SpatiallyConstantEvenSpacing tests.
161 Should be updated to use subTest when Python < 3.4 support is ended.
162 """
163 self.checkSpatiallyConstantEvenSpacing(0.0, 0.0)
164 self.checkSpatiallyConstantEvenSpacing(0.0, None)
165 self.checkSpatiallyConstantEvenSpacing(None, 0.0)
166 self.checkSpatiallyConstantEvenSpacing(None, None)
168 def checkSpatiallyConstantUnevenSpacing(self, throughputAtMin, throughputAtMax):
169 """Test that we can construct and use a spatially-constant
170 TransmissionCurve initialized with an unevenly-spaced wavelength
171 array.
172 """
173 throughputAtMin = self.randIfNone(throughputAtMin)
174 throughputAtMax = self.randIfNone(throughputAtMax)
175 wavelengths = self.minWavelength + (self.maxWavelength - self.minWavelength)*self.random.rand(100)
176 wavelengths.sort()
177 curve = makeTestCurve(self.random, self.minWavelength, self.maxWavelength,
178 throughputAtMin, throughputAtMax)
179 tc = self.makeAndCheckSpatiallyConstant(curve, wavelengths, throughputAtMin, throughputAtMax)
180 self.checkPersistence(tc)
182 def testSpatiallyConstantUnevenSpacing(self):
183 """Invoke all SpatiallyConstantUnevenSpacing tests.
185 Should be updated to use subTest when Python < 3.4 support is ended.
186 """
187 self.checkSpatiallyConstantUnevenSpacing(0.0, 0.0)
188 self.checkSpatiallyConstantUnevenSpacing(0.0, None)
189 self.checkSpatiallyConstantUnevenSpacing(None, 0.0)
190 self.checkSpatiallyConstantUnevenSpacing(None, None)
192 def checkProduct(self, throughputAtMin1, throughputAtMax1, throughputAtMin2, throughputAtMax2):
193 """Test the product of two spatially-constant TransmissionCurves."""
194 throughputAtMin1 = self.randIfNone(throughputAtMin1)
195 throughputAtMax1 = self.randIfNone(throughputAtMax1)
196 throughputAtMin2 = self.randIfNone(throughputAtMin2)
197 throughputAtMax2 = self.randIfNone(throughputAtMax2)
198 wl1a = 5100 + self.random.rand()
199 wl1b = 5500 + self.random.rand()
200 wl2a = 5200 + self.random.rand()
201 wl2b = 5600 + self.random.rand()
202 wl1 = np.linspace(wl1a, wl1b, 100)
203 wl2 = np.linspace(wl2a, wl2b, 100)
204 curve1 = makeTestCurve(self.random, wl1a, wl1b, throughputAtMin1, throughputAtMax1)
205 curve2 = makeTestCurve(self.random, wl2a, wl2b, throughputAtMin2, throughputAtMax2)
206 op1 = self.makeAndCheckSpatiallyConstant(curve1, wl1, throughputAtMin1, throughputAtMax1)
207 op2 = self.makeAndCheckSpatiallyConstant(curve2, wl2, throughputAtMin2, throughputAtMax2)
208 product = op1 * op2
210 lowest = np.linspace(wl1a - 10, wl1a - 1, 10)
211 self.checkEvaluation(product, lowest, throughputAtMin1*throughputAtMin2)
213 lower = np.linspace(wl1a + 1, wl2a - 1, 10)
214 if throughputAtMin2 == 0.0:
215 self.checkEvaluation(product, lower, 0.0)
216 else:
217 for point in self.points:
218 self.assertFloatsEqual(
219 op1.sampleAt(point, lower)*throughputAtMin2,
220 product.sampleAt(point, lower)
221 )
223 inner = np.linspace(wl2a, wl1b, 10)
224 for point in self.points:
225 self.assertFloatsAlmostEqual(
226 op1.sampleAt(point, inner)*op2.sampleAt(point, inner),
227 product.sampleAt(point, inner)
228 )
230 upper = np.linspace(wl1b + 1, wl2b - 1, 10)
231 if throughputAtMax1 == 0.0:
232 self.checkEvaluation(product, upper, 0.0)
233 else:
234 for point in self.points:
235 self.assertFloatsEqual(
236 op2.sampleAt(point, upper)*throughputAtMax1,
237 product.sampleAt(point, upper)
238 )
240 uppermost = np.linspace(wl2b + 1, wl2b + 10, 10)
241 self.checkEvaluation(product, uppermost, throughputAtMax1*throughputAtMax2)
243 self.checkPersistence(product)
245 def testProduct(self):
246 """Invoke all checkProduct tests.
248 Should be updated to use subTest when Python < 3.4 support is ended.
249 """
250 self.checkProduct(0.0, 0.0, 0.0, 0.0)
251 self.checkProduct(0.0, 0.0, 0.0, None)
252 self.checkProduct(0.0, 0.0, None, 0.0)
253 self.checkProduct(0.0, 0.0, None, None)
254 self.checkProduct(0.0, None, 0.0, 0.0)
255 self.checkProduct(0.0, None, 0.0, None)
256 self.checkProduct(0.0, None, None, 0.0)
257 self.checkProduct(0.0, None, None, None)
258 self.checkProduct(None, 0.0, 0.0, 0.0)
259 self.checkProduct(None, 0.0, 0.0, None)
260 self.checkProduct(None, 0.0, None, 0.0)
261 self.checkProduct(None, 0.0, None, None)
262 self.checkProduct(None, None, 0.0, 0.0)
263 self.checkProduct(None, None, 0.0, None)
264 self.checkProduct(None, None, None, 0.0)
265 self.checkProduct(None, None, None, None)
267 def makeRadial(self):
268 """Construct a random radial TransmissionCurve and return it with
269 the wavelengths, radii, and 2-d curve used to construct it.
270 """
271 wavelengths = np.linspace(self.minWavelength, self.maxWavelength, 100)
272 radii = np.linspace(0.0, 1.0, 200)
274 # This curve will represent the TransmissionCurve at the origin;
275 # we'll shift it to higher wavelengths and scale it linearly with radius
276 curve = makeTestCurve(self.random, wavelengths[0], wavelengths[90])
277 delta = (wavelengths[1] - wavelengths[0])/(radii[1] - radii[0])
279 def curve2d(lam, r):
280 return curve(lam + delta*r)*(1.0+r)
282 throughput = np.zeros(wavelengths.shape + radii.shape, dtype=float)
283 for i, radius in enumerate(radii):
284 throughput[:, i] = curve2d(wavelengths, radius)
286 tc = lsst.afw.image.TransmissionCurve.makeRadial(throughput, wavelengths, radii)
288 return tc, wavelengths, radii, curve2d
290 def testRadial(self):
291 """Test the functionality of radial TransmissionCurves."""
292 tc, wavelengths, radii, curve2d = self.makeRadial()
294 # Test at exactly the radii and wavelengths we initialized with.
295 for n, radius in enumerate(radii):
296 rot = lsst.geom.LinearTransform.makeRotation(
297 2.0*np.pi*self.random.rand()*lsst.geom.radians
298 )
299 p0 = lsst.geom.Point2D(0.0, radius)
300 p1 = rot(p0)
301 self.assertFloatsAlmostEqual(
302 curve2d(wavelengths, radius),
303 tc.sampleAt(p0, wavelengths),
304 rtol=1E-13
305 )
306 self.assertFloatsAlmostEqual(
307 curve2d(wavelengths, radius),
308 tc.sampleAt(p1, wavelengths),
309 rtol=1E-13
310 )
312 # Test at some other random points in radius and wavelength.
313 wl2 = np.linspace(self.minWavelength, self.maxWavelength, 151)
314 for point in self.points:
315 radius = (point.getX()**2 + point.getY()**2)**0.5
316 self.assertFloatsAlmostEqual(
317 curve2d(wl2, radius),
318 tc.sampleAt(point, wl2),
319 rtol=1E-2, atol=1E-2
320 )
322 # Test persistence for radial TransmissionCurves
323 self.checkPersistence(tc)
325 def testTransform(self):
326 """Test that we can transform a spatially-varying TransmissionCurve."""
327 tc, wavelengths, radii, curve2d = self.makeRadial()
329 # If we transform by a pure rotation, what we get back should be equivalent.
330 affine1 = lsst.geom.AffineTransform(
331 lsst.geom.LinearTransform.makeRotation(
332 2.0*np.pi*self.random.rand()*lsst.geom.radians
333 )
334 )
335 transform1 = lsst.afw.geom.makeTransform(affine1)
336 wl2 = np.linspace(self.minWavelength, self.maxWavelength, 151)
337 tc1 = tc.transformedBy(transform1)
338 self.assertTransmissionCurvesEqual(tc, tc1, rtol=1E-13)
340 # Test transforming by a random affine transform.
341 affine2 = lsst.geom.AffineTransform(
342 lsst.geom.LinearTransform.makeScaling(1.0 + self.random.rand()),
343 lsst.geom.Extent2D(self.random.randn(), self.random.randn())
344 )
345 transform2 = lsst.afw.geom.makeTransform(affine2)
346 tc2 = tc.transformedBy(transform2)
347 for point in self.points:
348 self.assertFloatsAlmostEqual(
349 tc.sampleAt(point, wl2),
350 tc2.sampleAt(affine2(point), wl2),
351 rtol=1E-13
352 )
354 # Test further transforming the rotated transmission curve
355 tc3 = tc1.transformedBy(transform2)
356 self.assertTransmissionCurvesEqual(tc2, tc3)
358 # Test persistence for transformed TransmissionCurves
359 self.checkPersistence(tc3)
361 def testExposure(self):
362 """Test that we can attach a TransmissionCurve to an Exposure and round-trip it through I/O."""
363 wavelengths = np.linspace(6200, 6400, 100)
364 curve = makeTestCurve(self.random, 6200, 6400, 0.0, 0.0)
365 tc1 = self.makeAndCheckSpatiallyConstant(curve, wavelengths, 0.0, 0.0)
366 exposure1 = lsst.afw.image.ExposureF(4, 5)
367 exposure1.getInfo().setTransmissionCurve(tc1)
368 self.assertTrue(exposure1.getInfo().hasTransmissionCurve())
369 with lsst.utils.tests.getTempFilePath(".fits") as filename:
370 exposure1.writeFits(filename)
371 exposure2 = lsst.afw.image.ExposureF(filename)
372 self.assertTrue(exposure2.getInfo().hasTransmissionCurve())
373 tc2 = exposure2.getInfo().getTransmissionCurve()
374 self.assertTransmissionCurvesEqual(tc1, tc2)
376 def testExposureRecord(self):
377 """Test that we can attach a TransmissionCurve to an ExposureRecord and round-trip it through I/O."""
378 wavelengths = np.linspace(6200, 6400, 100)
379 curve = makeTestCurve(self.random, 6200, 6400, 0.0, 0.0)
380 tc1 = self.makeAndCheckSpatiallyConstant(curve, wavelengths, 0.0, 0.0)
381 cat1 = lsst.afw.table.ExposureCatalog(lsst.afw.table.ExposureTable.makeMinimalSchema())
382 cat1.addNew().setTransmissionCurve(tc1)
383 self.assertTrue(cat1[0].getTransmissionCurve() is not None)
384 with lsst.utils.tests.getTempFilePath(".fits") as filename:
385 cat1.writeFits(filename)
386 cat2 = lsst.afw.table.ExposureCatalog.readFits(filename)
387 self.assertTrue(cat2[0].getTransmissionCurve() is not None)
388 tc2 = cat2[0].getTransmissionCurve()
389 self.assertTransmissionCurvesEqual(tc1, tc2)
392class MemoryTester(lsst.utils.tests.MemoryTestCase):
393 pass
396def setup_module(module):
397 lsst.utils.tests.init()
400if __name__ == "__main__": 400 ↛ 401line 400 didn't jump to line 401, because the condition on line 400 was never true
401 lsst.utils.tests.init()
402 unittest.main()