Coverage for tests/test_photometryMapping.py: 25%

188 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-08 02:41 -0800

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/>. 

21 

22import numpy as np 

23 

24import abc 

25import unittest 

26import lsst.utils.tests 

27 

28import lsst.geom 

29import lsst.jointcal 

30 

31 

32CHEBYSHEV_T = [ 32 ↛ exitline 32 didn't jump to the function exit

33 lambda x: 1, 

34 lambda x: x, 

35 lambda x: 2*x**2 - 1, 

36 lambda x: (4*x**2 - 3)*x, 

37 lambda x: (8*x**2 - 8)*x**2 + 1, 

38 lambda x: ((16*x**2 - 20)*x**2 + 5)*x, 

39] 

40 

41 

42class PhotometryMappingTestBase: 

43 def setUp(self): 

44 self.value = 5.0 

45 self.valueErr = 2.0 

46 

47 baseStar0 = lsst.jointcal.BaseStar(0, 0, 1, 2) 

48 self.star0 = lsst.jointcal.MeasuredStar(baseStar0) 

49 baseStar1 = lsst.jointcal.BaseStar(1, 2, 3, 4) 

50 self.star1 = lsst.jointcal.MeasuredStar(baseStar1) 

51 self.star1.setXFocal(2) 

52 self.star1.setYFocal(3) 

53 

54 

55class PhotometryMappingTestCase(PhotometryMappingTestBase, lsst.utils.tests.TestCase): 

56 def setUp(self): 

57 super(PhotometryMappingTestCase, self).setUp() 

58 self.scale = 3 

59 transform = lsst.jointcal.FluxTransformSpatiallyInvariant(self.scale) 

60 self.mapping = lsst.jointcal.PhotometryMapping(transform) 

61 

62 def test_getNpar(self): 

63 result = self.mapping.getNpar() 

64 self.assertEqual(result, 1) 

65 

66 def _test_offsetParams(self, delta, expect): 

67 self.mapping.offsetParams(delta) 

68 self.assertFloatsAlmostEqual(expect, self.mapping.getTransform().getParameters()) 

69 

70 def test_transform(self): 

71 result = self.mapping.transform(self.star0, self.value) 

72 self.assertEqual(result, self.value*self.scale) 

73 

74 def test_offsetParams(self): 

75 """Test offsetting; note that offsetParams offsets by `-delta`.""" 

76 delta = np.array([0.0]) 

77 self._test_offsetParams(delta, np.array([self.scale])) 

78 delta -= 1 

79 self._test_offsetParams(delta, self.scale-delta) 

80 

81 def test_computeParameterDerivatives(self): 

82 """Test that the derivative of a spatially invariant transform is always the same.""" 

83 result = self.mapping.computeParameterDerivatives(self.star0, self.value) 

84 self.assertEqual(self.value, result) 

85 result = self.mapping.computeParameterDerivatives(self.star1, self.value) 

86 self.assertEqual(self.value, result) 

87 transform = lsst.jointcal.FluxTransformSpatiallyInvariant(1000.0) 

88 mapping = lsst.jointcal.PhotometryMapping(transform) 

89 result = mapping.computeParameterDerivatives(self.star0, self.value) 

90 self.assertEqual(self.value, result) 

91 

92 def test_getMappingIndices(self): 

93 """A mapping with one invariant transform has one index""" 

94 self.mapping.setIndex(5) 

95 result = self.mapping.getMappingIndices() 

96 self.assertEqual(result, [5]) 

97 

98 

99class ChipVisitPhotometryMappingTestCase(PhotometryMappingTestBase, abc.ABC): 

100 def setUp(self): 

101 super().setUp() 

102 self.bbox = lsst.geom.Box2D(lsst.geom.Point2D(-5, -6), lsst.geom.Point2D(7, 8)) 

103 self.order = 1 

104 self.coefficients = np.array([[5, 2], [3, 0]], dtype=float) 

105 self.chipScale = 2 

106 self.visitScale = 3 

107 self.chipIndex = 5 

108 self.visitIndex = 1000 

109 

110 def _initMappings(self, InvariantTransform, ChebyTransform, ChipVisitMapping): 

111 """Initialize self.mappingInvariants and self.mappingCheby. 

112 Call after setUp(). 

113 

114 Parameters 

115 ---------- 

116 InvariantTransform : `PhotometryTransformSpatiallyInvariant`-type 

117 The PhotometryTransformSpatiallyInvariant-derived class to construct 

118 invariant transforms for. 

119 ChebyTransform : `PhotometryTransform`-type 

120 The PhotometryTransformChebyshev-derived class to construct 

121 2d transforms for. 

122 ChipVisitMapping : `PhotometryMapping`-type 

123 The PhotometryMapping-derived class to construct for both mappings. 

124 """ 

125 # self.mappingInvariants has two trivial transforms in it, to serve 

126 # as a simpler test of functionality. 

127 chipTransform = InvariantTransform(self.chipScale) 

128 chipMapping = lsst.jointcal.PhotometryMapping(chipTransform) 

129 chipMapping.setIndex(self.chipIndex) 

130 visitTransform = InvariantTransform(self.visitScale) 

131 visitMapping = lsst.jointcal.PhotometryMapping(visitTransform) 

132 visitMapping.setIndex(self.visitIndex) 

133 self.mappingInvariants = ChipVisitMapping(chipMapping, visitMapping) 

134 self.mappingInvariants.setWhatToFit(True, True) # default to fitting both 

135 

136 # self.mappingCheby is a more realistic mapping, with two components: 

137 # spatially-invariant per chip and a chebyshev per visit. 

138 # Need a new chipMapping, as it stores shared_ptr to the transform. 

139 chipTransform = InvariantTransform(self.chipScale) 

140 chipMapping = lsst.jointcal.PhotometryMapping(chipTransform) 

141 chipMapping.setIndex(self.chipIndex) 

142 visitTransform2 = ChebyTransform(self.coefficients, self.bbox) 

143 visitMapping2 = lsst.jointcal.PhotometryMapping(visitTransform2) 

144 visitMapping2.setIndex(self.visitIndex) 

145 self.mappingCheby = ChipVisitMapping(chipMapping, visitMapping2) 

146 self.mappingCheby.setWhatToFit(True, True) # default to fitting both 

147 

148 def test_getNpar(self): 

149 result = self.mappingInvariants.getNpar() 

150 self.assertEqual(result, 2) 

151 # order 1 implies 3 parameters, plus one for the chip mapping 

152 result = self.mappingCheby.getNpar() 

153 self.assertEqual(result, 4) 

154 

155 def _evaluate_chebyshev(self, x, y): 

156 """Evaluate the chebyshev defined by self.coefficients at (x,y)""" 

157 # sx, sy: transform from self.bbox range to [-1, -1] 

158 cx = (self.bbox.getMinX() + self.bbox.getMaxX())/2.0 

159 cy = (self.bbox.getMinY() + self.bbox.getMaxY())/2.0 

160 sx = 2.0 / self.bbox.getWidth() 

161 sy = 2.0 / self.bbox.getHeight() 

162 result = 0 

163 for j in range(self.order+1): 

164 Ty = CHEBYSHEV_T[j](sy*(y - cy)) 

165 for i in range(0, self.order-j+1): 

166 Tx = CHEBYSHEV_T[i](sx*(x - cx)) 

167 result += self.coefficients[j, i]*Tx*Ty 

168 return result 

169 

170 def _computeChebyshevDerivative(self, star): 

171 """Return the derivatives w.r.t. the Chebyshev components.""" 

172 cx = (self.bbox.getMinX() + self.bbox.getMaxX())/2.0 

173 cy = (self.bbox.getMinY() + self.bbox.getMaxY())/2.0 

174 sx = 2.0 / self.bbox.getWidth() 

175 sy = 2.0 / self.bbox.getHeight() 

176 Tx = np.array([CHEBYSHEV_T[i](sx*(star.getXFocal() - cx)) 

177 for i in range(self.order+1)], dtype=float) 

178 Ty = np.array([CHEBYSHEV_T[i](sy*(star.getYFocal() - cy)) 

179 for i in range(self.order+1)], dtype=float) 

180 expect = [] 

181 for j in range(len(Ty)): 

182 for i in range(0, self.order-j+1): 

183 expect.append(Ty[j]*Tx[i]) 

184 return np.array(expect) 

185 

186 @abc.abstractmethod 

187 def _computeVisitDerivative(self, star): 

188 """Return the derivative w.r.t. the chebyshev visit component.""" 

189 pass 

190 

191 @abc.abstractmethod 

192 def _computeChipDerivative(self, star): 

193 """Return the derivative w.r.t. the chip component.""" 

194 pass 

195 

196 def test_getMappingIndices(self): 

197 """There are npar indices in a constrained mapping.""" 

198 expect = [self.chipIndex, self.visitIndex] 

199 result = self.mappingInvariants.getMappingIndices() 

200 self.assertEqual(result, expect) 

201 

202 # npar - 1 because the chip mapping has the 1st parameter 

203 expect = [self.chipIndex, ] + list(range(self.visitIndex, 

204 self.visitIndex + self.mappingCheby.getNpar() - 1)) 

205 result = self.mappingCheby.getMappingIndices() 

206 self.assertEqual(result, expect) 

207 

208 def _test_transform_mappingInvariants(self, star, expect): 

209 result = self.mappingInvariants.transform(star, self.value) 

210 self.assertEqual(result, expect) 

211 

212 def _test_transform_mappingCheby(self, star, expect): 

213 result = self.mappingCheby.transform(star, self.value) 

214 self.assertEqual(result, expect) 

215 

216 def _test_computeParameterDerivatives(self, star, expectInvariant): 

217 """Test self.mappingInvariants and self.mappingCheby transforming star. 

218 expectCheby is calculated from _computeChipDerivative and 

219 _computeChebyshevDerivative. 

220 """ 

221 result = self.mappingInvariants.computeParameterDerivatives(star, self.value) 

222 self.assertFloatsAlmostEqual(result, expectInvariant) 

223 

224 # the chip derivative is a single number 

225 expectCheby = [self._computeChipDerivative(self.star1)] 

226 # the Chebyshev Derivatives are a list, so we have to use extend 

227 expectCheby.extend(self._computeVisitDerivative(self.star1)) 

228 expectCheby = np.array(expectCheby) 

229 result = self.mappingCheby.computeParameterDerivatives(star, self.value) 

230 self.assertFloatsAlmostEqual(result, expectCheby) 

231 

232 def _test_setWhatToFit(self, fittingChips, fittingVisits, nPar, indices, derivatives): 

233 """ 

234 Parameters 

235 ---------- 

236 fittingChips : `bool` 

237 Are we fitting the chip component? 

238 Passed to ``self.mappingCheby.setWhatToFit()``. 

239 fittingVisits : `bool` 

240 Are we fitting the visit component? 

241 Passed to ``self.mappingCheby.setWhatToFit()``. 

242 nPar : `int` 

243 Expected result from ``self.mappingCheby.getNpar()``. 

244 indices : `list` 

245 Expected result from ``self.mappingCheby.getMappingIndices()``. 

246 derivatives : `list` 

247 Expected result from ``self.mappingCheby.computeParameterDerivatives()``. 

248 """ 

249 self.mappingCheby.setWhatToFit(fittingChips, fittingVisits) 

250 self.assertEqual(self.mappingCheby.getNpar(), nPar) 

251 self.assertEqual(self.mappingCheby.getMappingIndices(), indices) 

252 result = self.mappingCheby.computeParameterDerivatives(self.star1, self.value) 

253 self.assertFloatsAlmostEqual(result, derivatives) 

254 

255 def test_setWhatToFit(self): 

256 """Test that mapping methods behave correctly when chip and/or visit 

257 fitting is disabled. 

258 

259 The "fit both" case (True, True) is tested by all of the above tests. 

260 """ 

261 # Using mappingCheby so getNpar() will distinguish chips (1 param) from visits (3 params). 

262 

263 # fit nothing means 0 parameters and no indices 

264 self._test_setWhatToFit(False, False, 0, [], []) 

265 

266 # fit just chips means 1 parameter and one index [self.chipIndex] 

267 self._test_setWhatToFit(True, False, 1, [self.chipIndex], 

268 np.array([self._computeChipDerivative(self.star1)])) 

269 

270 # fit just visits means 3 parameters (order 1) and 3 indices starting at self.visitIndex 

271 self._test_setWhatToFit(False, True, 3, list(range(self.visitIndex, self.visitIndex+3)), 

272 np.array([self._computeVisitDerivative(self.star1)])) 

273 

274 

275class ChipVisitFluxMappingTestCase(ChipVisitPhotometryMappingTestCase, lsst.utils.tests.TestCase): 

276 def setUp(self): 

277 super().setUp() 

278 self._initMappings(lsst.jointcal.FluxTransformSpatiallyInvariant, 

279 lsst.jointcal.FluxTransformChebyshev, 

280 lsst.jointcal.ChipVisitFluxMapping) 

281 

282 def _computeVisitDerivative(self, star): 

283 return self._computeChebyshevDerivative(star) * self.value * self.chipScale 

284 

285 def _computeChipDerivative(self, star): 

286 return self.value * self._evaluate_chebyshev(star.getXFocal(), star.getYFocal()) 

287 

288 def test_transform(self): 

289 expect = self.value * self.chipScale * self.visitScale 

290 self._test_transform_mappingInvariants(self.star0, expect) 

291 # The doubly-spatially invariant mapping should be independent of star position. 

292 self._test_transform_mappingInvariants(self.star1, expect) 

293 

294 expect = self.value * self.chipScale * self._evaluate_chebyshev(self.star0.getXFocal(), 

295 self.star0.getYFocal()) 

296 self._test_transform_mappingCheby(self.star0, expect) 

297 expect = self.value * self.chipScale * self._evaluate_chebyshev(self.star1.getXFocal(), 

298 self.star1.getYFocal()) 

299 self._test_transform_mappingCheby(self.star1, expect) 

300 

301 def test_computeParameterDerivatives(self): 

302 expectInvariant = np.array([self.value*self.visitScale, self.value*self.chipScale]) 

303 self._test_computeParameterDerivatives(self.star1, expectInvariant) 

304 

305 

306class ChipVisitMagnitudeMappingTestCase(ChipVisitPhotometryMappingTestCase, lsst.utils.tests.TestCase): 

307 def setUp(self): 

308 super().setUp() 

309 self._initMappings(lsst.jointcal.MagnitudeTransformSpatiallyInvariant, 

310 lsst.jointcal.MagnitudeTransformChebyshev, 

311 lsst.jointcal.ChipVisitMagnitudeMapping) 

312 

313 def _computeVisitDerivative(self, star): 

314 return self._computeChebyshevDerivative(star) 

315 

316 def _computeChipDerivative(self, star): 

317 # Magnitude chip derivative is always identically 1: 

318 # d(M(m))/d(m0)=1 where M(m) = m + m0 

319 return 1.0 

320 

321 def test_transform(self): 

322 expect = self.value + self.chipScale + self.visitScale 

323 self._test_transform_mappingInvariants(self.star0, expect) 

324 # The doubly-spatially invariant mapping should be independent of star position. 

325 self._test_transform_mappingInvariants(self.star1, expect) 

326 

327 expect = self.value + self.chipScale + self._evaluate_chebyshev(self.star0.getXFocal(), 

328 self.star0.getYFocal()) 

329 self._test_transform_mappingCheby(self.star0, expect) 

330 

331 expect = self.value + self.chipScale + self._evaluate_chebyshev(self.star1.getXFocal(), 

332 self.star1.getYFocal()) 

333 self._test_transform_mappingCheby(self.star1, expect) 

334 

335 def test_computeParameterDerivatives(self): 

336 # the parameter derivative of a spatially invariant magnitude transform is always 1. 

337 expectInvariant = np.array([1.0, 1.0]) 

338 self._test_computeParameterDerivatives(self.star1, expectInvariant) 

339 

340 

341class MemoryTester(lsst.utils.tests.MemoryTestCase): 

342 pass 

343 

344 

345def setup_module(module): 

346 lsst.utils.tests.init() 

347 

348 

349if __name__ == "__main__": 349 ↛ 350line 349 didn't jump to line 350, because the condition on line 349 was never true

350 lsst.utils.tests.init() 

351 unittest.main()