Coverage for tests/test_photometryMapping.py: 30%
190 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-25 02:08 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-25 02:08 -0700
1# This file is part of jointcal.
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 numpy as np
24import abc
25import unittest
26import lsst.utils.tests
28import lsst.geom
29import lsst.jointcal.photometryMappings
30import lsst.jointcal.photometryTransform
31import lsst.jointcal.star
34CHEBYSHEV_T = [ 34 ↛ exitline 34 didn't jump to the function exit
35 lambda x: 1,
36 lambda x: x,
37 lambda x: 2*x**2 - 1,
38 lambda x: (4*x**2 - 3)*x,
39 lambda x: (8*x**2 - 8)*x**2 + 1,
40 lambda x: ((16*x**2 - 20)*x**2 + 5)*x,
41]
44class PhotometryMappingTestBase:
45 def setUp(self):
46 self.value = 5.0
47 self.valueErr = 2.0
49 baseStar0 = lsst.jointcal.star.BaseStar(0, 0, 1, 2)
50 self.star0 = lsst.jointcal.star.MeasuredStar(baseStar0)
51 baseStar1 = lsst.jointcal.star.BaseStar(1, 2, 3, 4)
52 self.star1 = lsst.jointcal.star.MeasuredStar(baseStar1)
53 self.star1.setXFocal(2)
54 self.star1.setYFocal(3)
57class PhotometryMappingTestCase(PhotometryMappingTestBase, lsst.utils.tests.TestCase):
58 def setUp(self):
59 super(PhotometryMappingTestCase, self).setUp()
60 self.scale = 3
61 transform = lsst.jointcal.photometryTransform.FluxTransformSpatiallyInvariant(self.scale)
62 self.mapping = lsst.jointcal.photometryMappings.PhotometryMapping(transform)
64 def test_getNpar(self):
65 result = self.mapping.getNpar()
66 self.assertEqual(result, 1)
68 def _test_offsetParams(self, delta, expect):
69 self.mapping.offsetParams(delta)
70 self.assertFloatsAlmostEqual(expect, self.mapping.getTransform().getParameters())
72 def test_transform(self):
73 result = self.mapping.transform(self.star0, self.value)
74 self.assertEqual(result, self.value*self.scale)
76 def test_offsetParams(self):
77 """Test offsetting; note that offsetParams offsets by `-delta`."""
78 delta = np.array([0.0])
79 self._test_offsetParams(delta, np.array([self.scale]))
80 delta -= 1
81 self._test_offsetParams(delta, self.scale-delta)
83 def test_computeParameterDerivatives(self):
84 """Test that the derivative of a spatially invariant transform is always the same."""
85 result = self.mapping.computeParameterDerivatives(self.star0, self.value)
86 self.assertEqual(self.value, result)
87 result = self.mapping.computeParameterDerivatives(self.star1, self.value)
88 self.assertEqual(self.value, result)
89 transform = lsst.jointcal.FluxTransformSpatiallyInvariant(1000.0)
90 mapping = lsst.jointcal.PhotometryMapping(transform)
91 result = mapping.computeParameterDerivatives(self.star0, self.value)
92 self.assertEqual(self.value, result)
94 def test_getMappingIndices(self):
95 """A mapping with one invariant transform has one index"""
96 self.mapping.setIndex(5)
97 result = self.mapping.getMappingIndices()
98 self.assertEqual(result, [5])
101class ChipVisitPhotometryMappingTestCase(PhotometryMappingTestBase, abc.ABC):
102 def setUp(self):
103 super().setUp()
104 self.bbox = lsst.geom.Box2D(lsst.geom.Point2D(-5, -6), lsst.geom.Point2D(7, 8))
105 self.order = 1
106 self.coefficients = np.array([[5, 2], [3, 0]], dtype=float)
107 self.chipScale = 2
108 self.visitScale = 3
109 self.chipIndex = 5
110 self.visitIndex = 1000
112 def _initMappings(self, InvariantTransform, ChebyTransform, ChipVisitMapping):
113 """Initialize self.mappingInvariants and self.mappingCheby.
114 Call after setUp().
116 Parameters
117 ----------
118 InvariantTransform : `PhotometryTransformSpatiallyInvariant`-type
119 The PhotometryTransformSpatiallyInvariant-derived class to construct
120 invariant transforms for.
121 ChebyTransform : `PhotometryTransform`-type
122 The PhotometryTransformChebyshev-derived class to construct
123 2d transforms for.
124 ChipVisitMapping : `PhotometryMapping`-type
125 The PhotometryMapping-derived class to construct for both mappings.
126 """
127 # self.mappingInvariants has two trivial transforms in it, to serve
128 # as a simpler test of functionality.
129 chipTransform = InvariantTransform(self.chipScale)
130 chipMapping = lsst.jointcal.PhotometryMapping(chipTransform)
131 chipMapping.setIndex(self.chipIndex)
132 visitTransform = InvariantTransform(self.visitScale)
133 visitMapping = lsst.jointcal.PhotometryMapping(visitTransform)
134 visitMapping.setIndex(self.visitIndex)
135 self.mappingInvariants = ChipVisitMapping(chipMapping, visitMapping)
136 self.mappingInvariants.setWhatToFit(True, True) # default to fitting both
138 # self.mappingCheby is a more realistic mapping, with two components:
139 # spatially-invariant per chip and a chebyshev per visit.
140 # Need a new chipMapping, as it stores shared_ptr to the transform.
141 chipTransform = InvariantTransform(self.chipScale)
142 chipMapping = lsst.jointcal.PhotometryMapping(chipTransform)
143 chipMapping.setIndex(self.chipIndex)
144 visitTransform2 = ChebyTransform(self.coefficients, self.bbox)
145 visitMapping2 = lsst.jointcal.PhotometryMapping(visitTransform2)
146 visitMapping2.setIndex(self.visitIndex)
147 self.mappingCheby = ChipVisitMapping(chipMapping, visitMapping2)
148 self.mappingCheby.setWhatToFit(True, True) # default to fitting both
150 def test_getNpar(self):
151 result = self.mappingInvariants.getNpar()
152 self.assertEqual(result, 2)
153 # order 1 implies 3 parameters, plus one for the chip mapping
154 result = self.mappingCheby.getNpar()
155 self.assertEqual(result, 4)
157 def _evaluate_chebyshev(self, x, y):
158 """Evaluate the chebyshev defined by self.coefficients at (x,y)"""
159 # sx, sy: transform from self.bbox range to [-1, -1]
160 cx = (self.bbox.getMinX() + self.bbox.getMaxX())/2.0
161 cy = (self.bbox.getMinY() + self.bbox.getMaxY())/2.0
162 sx = 2.0 / self.bbox.getWidth()
163 sy = 2.0 / self.bbox.getHeight()
164 result = 0
165 for j in range(self.order+1):
166 Ty = CHEBYSHEV_T[j](sy*(y - cy))
167 for i in range(0, self.order-j+1):
168 Tx = CHEBYSHEV_T[i](sx*(x - cx))
169 result += self.coefficients[j, i]*Tx*Ty
170 return result
172 def _computeChebyshevDerivative(self, star):
173 """Return the derivatives w.r.t. the Chebyshev components."""
174 cx = (self.bbox.getMinX() + self.bbox.getMaxX())/2.0
175 cy = (self.bbox.getMinY() + self.bbox.getMaxY())/2.0
176 sx = 2.0 / self.bbox.getWidth()
177 sy = 2.0 / self.bbox.getHeight()
178 Tx = np.array([CHEBYSHEV_T[i](sx*(star.getXFocal() - cx))
179 for i in range(self.order+1)], dtype=float)
180 Ty = np.array([CHEBYSHEV_T[i](sy*(star.getYFocal() - cy))
181 for i in range(self.order+1)], dtype=float)
182 expect = []
183 for j in range(len(Ty)):
184 for i in range(0, self.order-j+1):
185 expect.append(Ty[j]*Tx[i])
186 return np.array(expect)
188 @abc.abstractmethod
189 def _computeVisitDerivative(self, star):
190 """Return the derivative w.r.t. the chebyshev visit component."""
191 pass
193 @abc.abstractmethod
194 def _computeChipDerivative(self, star):
195 """Return the derivative w.r.t. the chip component."""
196 pass
198 def test_getMappingIndices(self):
199 """There are npar indices in a constrained mapping."""
200 expect = [self.chipIndex, self.visitIndex]
201 result = self.mappingInvariants.getMappingIndices()
202 self.assertEqual(result, expect)
204 # npar - 1 because the chip mapping has the 1st parameter
205 expect = [self.chipIndex, ] + list(range(self.visitIndex,
206 self.visitIndex + self.mappingCheby.getNpar() - 1))
207 result = self.mappingCheby.getMappingIndices()
208 self.assertEqual(result, expect)
210 def _test_transform_mappingInvariants(self, star, expect):
211 result = self.mappingInvariants.transform(star, self.value)
212 self.assertEqual(result, expect)
214 def _test_transform_mappingCheby(self, star, expect):
215 result = self.mappingCheby.transform(star, self.value)
216 self.assertEqual(result, expect)
218 def _test_computeParameterDerivatives(self, star, expectInvariant):
219 """Test self.mappingInvariants and self.mappingCheby transforming star.
220 expectCheby is calculated from _computeChipDerivative and
221 _computeChebyshevDerivative.
222 """
223 result = self.mappingInvariants.computeParameterDerivatives(star, self.value)
224 self.assertFloatsAlmostEqual(result, expectInvariant)
226 # the chip derivative is a single number
227 expectCheby = [self._computeChipDerivative(self.star1)]
228 # the Chebyshev Derivatives are a list, so we have to use extend
229 expectCheby.extend(self._computeVisitDerivative(self.star1))
230 expectCheby = np.array(expectCheby)
231 result = self.mappingCheby.computeParameterDerivatives(star, self.value)
232 self.assertFloatsAlmostEqual(result, expectCheby)
234 def _test_setWhatToFit(self, fittingChips, fittingVisits, nPar, indices, derivatives):
235 """
236 Parameters
237 ----------
238 fittingChips : `bool`
239 Are we fitting the chip component?
240 Passed to ``self.mappingCheby.setWhatToFit()``.
241 fittingVisits : `bool`
242 Are we fitting the visit component?
243 Passed to ``self.mappingCheby.setWhatToFit()``.
244 nPar : `int`
245 Expected result from ``self.mappingCheby.getNpar()``.
246 indices : `list`
247 Expected result from ``self.mappingCheby.getMappingIndices()``.
248 derivatives : `list`
249 Expected result from ``self.mappingCheby.computeParameterDerivatives()``.
250 """
251 self.mappingCheby.setWhatToFit(fittingChips, fittingVisits)
252 self.assertEqual(self.mappingCheby.getNpar(), nPar)
253 self.assertEqual(self.mappingCheby.getMappingIndices(), indices)
254 result = self.mappingCheby.computeParameterDerivatives(self.star1, self.value)
255 self.assertFloatsAlmostEqual(result, derivatives)
257 def test_setWhatToFit(self):
258 """Test that mapping methods behave correctly when chip and/or visit
259 fitting is disabled.
261 The "fit both" case (True, True) is tested by all of the above tests.
262 """
263 # Using mappingCheby so getNpar() will distinguish chips (1 param) from visits (3 params).
265 # fit nothing means 0 parameters and no indices
266 self._test_setWhatToFit(False, False, 0, [], [])
268 # fit just chips means 1 parameter and one index [self.chipIndex]
269 self._test_setWhatToFit(True, False, 1, [self.chipIndex],
270 np.array([self._computeChipDerivative(self.star1)]))
272 # fit just visits means 3 parameters (order 1) and 3 indices starting at self.visitIndex
273 self._test_setWhatToFit(False, True, 3, list(range(self.visitIndex, self.visitIndex+3)),
274 np.array([self._computeVisitDerivative(self.star1)]))
277class ChipVisitFluxMappingTestCase(ChipVisitPhotometryMappingTestCase, lsst.utils.tests.TestCase):
278 def setUp(self):
279 super().setUp()
280 self._initMappings(lsst.jointcal.FluxTransformSpatiallyInvariant,
281 lsst.jointcal.FluxTransformChebyshev,
282 lsst.jointcal.ChipVisitFluxMapping)
284 def _computeVisitDerivative(self, star):
285 return self._computeChebyshevDerivative(star) * self.value * self.chipScale
287 def _computeChipDerivative(self, star):
288 return self.value * self._evaluate_chebyshev(star.getXFocal(), star.getYFocal())
290 def test_transform(self):
291 expect = self.value * self.chipScale * self.visitScale
292 self._test_transform_mappingInvariants(self.star0, expect)
293 # The doubly-spatially invariant mapping should be independent of star position.
294 self._test_transform_mappingInvariants(self.star1, expect)
296 expect = self.value * self.chipScale * self._evaluate_chebyshev(self.star0.getXFocal(),
297 self.star0.getYFocal())
298 self._test_transform_mappingCheby(self.star0, expect)
299 expect = self.value * self.chipScale * self._evaluate_chebyshev(self.star1.getXFocal(),
300 self.star1.getYFocal())
301 self._test_transform_mappingCheby(self.star1, expect)
303 def test_computeParameterDerivatives(self):
304 expectInvariant = np.array([self.value*self.visitScale, self.value*self.chipScale])
305 self._test_computeParameterDerivatives(self.star1, expectInvariant)
308class ChipVisitMagnitudeMappingTestCase(ChipVisitPhotometryMappingTestCase, lsst.utils.tests.TestCase):
309 def setUp(self):
310 super().setUp()
311 self._initMappings(lsst.jointcal.MagnitudeTransformSpatiallyInvariant,
312 lsst.jointcal.MagnitudeTransformChebyshev,
313 lsst.jointcal.ChipVisitMagnitudeMapping)
315 def _computeVisitDerivative(self, star):
316 return self._computeChebyshevDerivative(star)
318 def _computeChipDerivative(self, star):
319 # Magnitude chip derivative is always identically 1:
320 # d(M(m))/d(m0)=1 where M(m) = m + m0
321 return 1.0
323 def test_transform(self):
324 expect = self.value + self.chipScale + self.visitScale
325 self._test_transform_mappingInvariants(self.star0, expect)
326 # The doubly-spatially invariant mapping should be independent of star position.
327 self._test_transform_mappingInvariants(self.star1, expect)
329 expect = self.value + self.chipScale + self._evaluate_chebyshev(self.star0.getXFocal(),
330 self.star0.getYFocal())
331 self._test_transform_mappingCheby(self.star0, expect)
333 expect = self.value + self.chipScale + self._evaluate_chebyshev(self.star1.getXFocal(),
334 self.star1.getYFocal())
335 self._test_transform_mappingCheby(self.star1, expect)
337 def test_computeParameterDerivatives(self):
338 # the parameter derivative of a spatially invariant magnitude transform is always 1.
339 expectInvariant = np.array([1.0, 1.0])
340 self._test_computeParameterDerivatives(self.star1, expectInvariant)
343class MemoryTester(lsst.utils.tests.MemoryTestCase):
344 pass
347def setup_module(module):
348 lsst.utils.tests.init()
351if __name__ == "__main__": 351 ↛ 352line 351 didn't jump to line 352, because the condition on line 351 was never true
352 lsst.utils.tests.init()
353 unittest.main()