Coverage for tests/test_coordinateConverter.py: 8%

358 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-14 02:50 -0800

1# This file is part of cbp. 

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 itertools 

23import math 

24import unittest 

25 

26import numpy as np 

27 

28from lsst.sphgeom import Vector3d 

29from lsst.afw.cameraGeom import FIELD_ANGLE, FOCAL_PLANE 

30from lsst.geom import Box2D, Point2D, SpherePoint, arcseconds, degrees, radians 

31import lsst.cbp.coordUtils as coordUtils 

32from lsst.cbp.testUtils import SampleCoordinateConverter 

33import lsst.cbp.coordinateConverter 

34import lsst.utils.tests 

35 

36# set True to record and report numeric error in convertVectorFromPupilToBase 

37# and in the iterative component of setFocalFieldAngle 

38ReportRecordedErrors = True 

39 

40 

41class CoordConverterTestCase(lsst.utils.tests.TestCase): 

42 def setUp(self): 

43 if ReportRecordedErrors: 

44 lsst.cbp.coordUtils.startRecordingErrors() 

45 lsst.cbp.coordinateConverter.startRecordingErrors() 

46 self.scc = SampleCoordinateConverter() 

47 self.cco = self.scc.coordinateConverter 

48 # a list of all detector names plus None for the default detector 

49 self.detectorNames = itertools.chain([None], self.cco.cameraGeom.getNameIter()) 

50 # set values for maximum error that are "plenty good enough" 

51 self.maxPupilPosErr = 1e-4 # mm 

52 self.maxDetectorPosErr = 1e-4 # pixels 

53 self.maxFocalPlanePosErr = self.scc.pixelSizeMm * self.maxDetectorPosErr # mm 

54 self.maxFieldAngleErrRad = (0.001*arcseconds).asRadians() 

55 

56 def tearDown(self): 

57 if ReportRecordedErrors: 

58 utilsErrList = coordUtils.getRecordedErrors() 

59 coordUtils.stopRecordingErrors() 

60 if len(utilsErrList) > 0: 

61 print("\nWarning: recorded {} numerical errors in computeAzAltFromBasePupil;" 

62 " the worst 5 are:".format(len(utilsErrList))) 

63 for err, vectorBase, vectorPupil in utilsErrList[-5:]: 

64 print("error={:0.5f} arcsec, vectorBase={}, vectorPupil={}".format( 

65 err, vectorBase, vectorPupil)) 

66 

67 errorList = lsst.cbp.coordinateConverter.getRecordedErrors() 

68 if len(errorList) > 0: 

69 errorArray = np.array([val[0] for val in errorList]) 

70 print("\nroot finder mean error={:0.3g}, sdev={:0.3g}, min={:0.3g}, max={:0.3g}".format( 

71 errorArray.mean(), errorArray.std(), errorArray.min(), errorArray.max())) 

72 del self.cco 

73 del self.scc 

74 

75 def testOffsetDetectorPos(self): 

76 pupilPos = (3000, 4000) 

77 detectorPos = (400, 550) 

78 for pupilOffset, detectorOffset, detector, beam in itertools.product( 

79 (None, (0, 0), (500, -300)), 

80 (None, (0, 0), (-50, 350)), 

81 self.detectorNames, 

82 self.cco.beamNames, 

83 ): 

84 with self.subTest(pupilOffset=pupilOffset, detectorPos=detectorPos, detector=detector, beam=beam): 

85 self.cco.setDetectorPos(pupilPos=pupilPos, detectorPos=detectorPos, detector=detector, 

86 beam=beam) 

87 initialBeamInfo = self.cco[beam] 

88 self.assertPairsAlmostEqual(initialBeamInfo.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

89 self.assertPairsAlmostEqual(initialBeamInfo.detectorPos, detectorPos, 

90 maxDiff=self.maxDetectorPosErr) 

91 self.cco.offsetDetectorPos(pupilOffset=pupilOffset, detectorOffset=detectorOffset, 

92 beam=beam) 

93 finalBeamInfo = self.cco[beam] 

94 if pupilOffset is None: 

95 desiredPupilPos = pupilPos 

96 else: 

97 desiredPupilPos = np.add(pupilPos, pupilOffset) 

98 if detectorOffset is None: 

99 desiredDetectorPos = detectorPos 

100 else: 

101 desiredDetectorPos = np.add(detectorPos, detectorOffset) 

102 self.assertPairsAlmostEqual(finalBeamInfo.pupilPos, desiredPupilPos, 

103 maxDiff=self.maxPupilPosErr) 

104 self.assertPairsAlmostEqual(finalBeamInfo.detectorPos, desiredDetectorPos, 

105 maxDiff=self.maxDetectorPosErr) 

106 

107 def testOffsetFocalFieldAngle(self): 

108 pupilPos = (3000, 4000) 

109 focalFieldAngle = (-0.03, 0.04) 

110 for pupilOffset, focalFieldAngleOffset, beam in itertools.product( 

111 (None, (0, 0), (500, -300)), 

112 (None, (0, 0), (0.02, -0.03)), 

113 self.cco.beamNames, 

114 ): 

115 with self.subTest(pupilOffset=pupilOffset, focalFieldAngleOffset=focalFieldAngleOffset, 

116 beam=beam): 

117 self.cco.setFocalFieldAngle(pupilPos=pupilPos, focalFieldAngle=focalFieldAngle, beam=beam) 

118 initialBeamInfo = self.cco[beam] 

119 self.assertPairsAlmostEqual(initialBeamInfo.pupilPos, pupilPos, 

120 maxDiff=self.maxPupilPosErr) 

121 self.assertPairsAlmostEqual(initialBeamInfo.focalFieldAngle, focalFieldAngle, 

122 maxDiff=self.maxFieldAngleErrRad) 

123 

124 self.cco.offsetFocalFieldAngle(pupilOffset=pupilOffset, 

125 focalFieldAngleOffset=focalFieldAngleOffset, beam=beam) 

126 finalBeamInfo = self.cco[beam] 

127 if pupilOffset is None: 

128 desiredPupilPos = pupilPos 

129 else: 

130 desiredPupilPos = np.add(pupilPos, pupilOffset) 

131 if focalFieldAngleOffset is None: 

132 desiredFocalFieldAngle = focalFieldAngle 

133 else: 

134 desiredFocalFieldAngle = np.add(focalFieldAngle, focalFieldAngleOffset) 

135 self.assertPairsAlmostEqual(finalBeamInfo.pupilPos, desiredPupilPos, 

136 maxDiff=self.maxPupilPosErr) 

137 self.assertPairsAlmostEqual(finalBeamInfo.focalFieldAngle, desiredFocalFieldAngle, 

138 maxDiff=self.maxFieldAngleErrRad) 

139 

140 def testOffsetFocalPlanePos(self): 

141 pupilPos = (3000, 4000) 

142 focalPlanePos = (-400, 550) 

143 for pupilOffset, focalPlaneOffset, beam in itertools.product( 

144 (None, (0, 0), (500, -300)), 

145 (None, (0, 0), (22, -350)), 

146 self.cco.beamNames, 

147 ): 

148 with self.subTest(pupilOffset=pupilOffset, focalPlanePos=focalPlanePos, beam=beam): 

149 self.cco.setFocalPlanePos(pupilPos=pupilPos, focalPlanePos=focalPlanePos, beam=beam) 

150 initialBeamInfo = self.cco[beam] 

151 self.assertPairsAlmostEqual(initialBeamInfo.pupilPos, pupilPos, 

152 maxDiff=self.maxPupilPosErr) 

153 self.assertPairsAlmostEqual(initialBeamInfo.focalPlanePos, focalPlanePos, 

154 maxDiff=self.maxFocalPlanePosErr) 

155 self.cco.offsetFocalPlanePos(pupilOffset=pupilOffset, focalPlaneOffset=focalPlaneOffset, 

156 beam=beam) 

157 finalBeamInfo = self.cco[beam] 

158 if pupilOffset is None: 

159 desiredPupilPos = pupilPos 

160 else: 

161 desiredPupilPos = np.add(pupilPos, pupilOffset) 

162 if focalPlaneOffset is None: 

163 desiredFocalPlanePos = focalPlanePos 

164 else: 

165 desiredFocalPlanePos = np.add(focalPlanePos, focalPlaneOffset) 

166 self.assertPairsAlmostEqual(finalBeamInfo.pupilPos, desiredPupilPos, 

167 maxDiff=self.maxPupilPosErr) 

168 self.assertPairsAlmostEqual(finalBeamInfo.focalPlanePos, desiredFocalPlanePos, 

169 maxDiff=self.maxFocalPlanePosErr) 

170 

171 def testSampleBasics(self): 

172 """Test basic elements of the sample coordinate converter 

173 """ 

174 self.assertEqual(len(self.cco.cameraGeom), len(self.scc.detectorFracPosList)) 

175 self.assertEqual(set(self.cco.cameraGeom.getNameIter()), 

176 set("D" + str(i) for i in range(len(self.scc.detectorFracPosList)))) 

177 self.assertEqual(len(self.cco), 6) 

178 self.assertEqual(tuple(self.cco.beamNames), ("beam0", "beam1", "beam2", "beam3", "beam4", "beam5")) 

179 self.assertEqual(self.cco.maskInfo.numHoles, 6) 

180 self.assertPairsAlmostEqual(self.cco.maskInfo.getHolePos("beam0"), (0, 0)) 

181 for beamInfo, beam in zip(self.cco, self.cco.beamNames): 

182 self.assertEqual(beamInfo.name, beam) 

183 

184 def testSetPupilFieldAngleTrivial(self): 

185 """Test setPupilFieldAngle for the trivial case of hole 0 

186 aimed perpendicular to the center of the pupil 

187 """ 

188 self.cco.setPupilFieldAngle(pupilPos=(0, 0)) 

189 

190 # The telescope should be pointed at the center of the CBP 

191 # and vice-versa. 

192 # NOTE: It would be nice to get better than the 0.0028" that I measure 

193 self.assertSpherePointsAlmostEqual(self.cco.telAzAltInternal, 

194 SpherePoint(Vector3d(*self.cco.config.cbpPosition)), 

195 maxSep=0.01*arcseconds) 

196 self.assertSpherePointsAlmostEqual(self.cco.cbpAzAltInternal, 

197 SpherePoint(Vector3d(*(-self.cco.config.cbpPosition))), 

198 maxSep=0.01*arcseconds) 

199 

200 self.assertAnglesAlmostEqual(self.cco.telRotInternal, 0*degrees) 

201 

202 # Beam 0 should be pointed to the center of the pupil, 

203 # normal to the pupil, and land on the center of the focal plane 

204 # and the center of detector D0. 

205 beamInfo0 = self.cco[0] 

206 self.assertEqual(beamInfo0.name, "beam0") 

207 self.assertPairsAlmostEqual(beamInfo0.holePos, (0, 0)) 

208 self.assertFalse(beamInfo0.isOnPupil) # blocked by the central obscuration 

209 self.assertTrue(beamInfo0.isOnFocalPlane) 

210 self.assertTrue(beamInfo0.isOnDetector) 

211 self.assertFalse(beamInfo0.isVisible) # blocked by the central obscuration 

212 self.assertPairsAlmostEqual(beamInfo0.focalPlanePos, (0, 0), maxDiff=self.maxFocalPlanePosErr) 

213 self.assertPairsAlmostEqual(beamInfo0.focalFieldAngle, (0, 0), maxDiff=self.maxFieldAngleErrRad) 

214 self.assertPairsAlmostEqual(beamInfo0.pupilFieldAngle, (0, 0), maxDiff=self.maxFieldAngleErrRad) 

215 self.assertPairsAlmostEqual(beamInfo0.pupilPos, (0, 0), maxDiff=self.maxPupilPosErr) 

216 self.assertEqual(beamInfo0.detectorName, "D0") 

217 bboxd = Box2D(self.cco.cameraGeom["D0"].getBBox()) 

218 detectorCtrPos = bboxd.getCenter() 

219 detector34Pos = bboxd.getMin() + bboxd.getDimensions()*0.75 

220 self.assertPairsAlmostEqual(beamInfo0.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) 

221 

222 # Beam 1 should land on detector D0, 3/4 of the way from LL to UR. 

223 beamInfo1 = self.cco[1] 

224 self.assertEqual(beamInfo1.name, "beam1") 

225 self.assertTrue(beamInfo1.isOnDetector) 

226 self.assertEqual(beamInfo1.detectorName, "D0") 

227 self.assertPairsAlmostEqual(beamInfo1.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) 

228 

229 # Beam 2 should land on the center of detector D1. 

230 beamInfo2 = self.cco[2] 

231 self.assertEqual(beamInfo2.name, "beam2") 

232 self.assertTrue(beamInfo2.isOnDetector) 

233 self.assertEqual(beamInfo2.detectorName, "D1") 

234 self.assertPairsAlmostEqual(beamInfo2.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) 

235 

236 # Beam 3 should land on detector D1, 3/4 of the way from LL to UR. 

237 beamInfo3 = self.cco[3] 

238 self.assertEqual(beamInfo3.name, "beam3") 

239 self.assertTrue(beamInfo3.isOnDetector) 

240 self.assertEqual(beamInfo3.detectorName, "D1") 

241 self.assertPairsAlmostEqual(beamInfo3.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) 

242 

243 # beam 4 should land on the center of detector D2 

244 beamInfo4 = self.cco[4] 

245 self.assertEqual(beamInfo4.name, "beam4") 

246 self.assertTrue(beamInfo4.isOnDetector) 

247 self.assertEqual(beamInfo4.detectorName, "D2") 

248 self.assertPairsAlmostEqual(beamInfo4.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) 

249 

250 # Beam 5 should land on detector D2, 3/4 of the way from LL to UR 

251 # The measured error is 5e-7 pixels, which is fine. 

252 # I strongly suspect it is due to inaccuracy in the inverse of the 

253 # field angle to focal plane transform. 

254 beamInfo5 = self.cco[5] 

255 self.assertEqual(beamInfo5.name, "beam5") 

256 self.assertTrue(beamInfo5.isOnDetector) 

257 self.assertEqual(beamInfo5.detectorName, "D2") 

258 self.assertPairsAlmostEqual(beamInfo5.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) 

259 

260 def testSetFocalFieldAngle(self): 

261 fieldAngleToFocalPlane = self.cco.cameraGeom.getTransform(FIELD_ANGLE, FOCAL_PLANE) 

262 for focalFieldAngle in ((0, 0), (0, 0.05), (-0.05, -0.03)): 

263 desiredFocalPlanePos = fieldAngleToFocalPlane.applyForward(Point2D(*focalFieldAngle)) 

264 for pupilPos, beam in itertools.product( 

265 ((0, 0), (0, 5000), (-5000, 0), (5000, -5000)), 

266 self.cco.beamNames, 

267 ): 

268 with self.subTest(focalFieldAngle=focalFieldAngle, pupilPos=pupilPos, beam=beam): 

269 self.cco.setFocalFieldAngle(pupilPos=pupilPos, focalFieldAngle=focalFieldAngle, beam=beam) 

270 beamInfo = self.cco[beam] 

271 self.assertPairsAlmostEqual(beamInfo.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

272 self.assertPairsAlmostEqual(beamInfo.focalFieldAngle, focalFieldAngle, 

273 maxDiff=self.maxFieldAngleErrRad) 

274 self.assertPairsAlmostEqual(beamInfo.focalPlanePos, desiredFocalPlanePos, 

275 maxDiff=self.maxFocalPlanePosErr) 

276 self.checkOrientation() 

277 

278 def testSetDetectorPos(self): 

279 for detectorPos, pupilPos, detector in itertools.product( 

280 ((0, 0), (500, 1000), (25, 1850)), 

281 ((0, 0), (0, 5000), (-5000, 0), (5000, -5000)), 

282 self.detectorNames, 

283 ): 

284 with self.subTest(detectorPos=detectorPos, pupilPos=pupilPos, detector=detector): 

285 if detector is None: 

286 desiredDetector = self.cco.config.defaultDetector 

287 else: 

288 desiredDetector = detector 

289 for pupilPos in ( 

290 ): 

291 for beam in range(4): 

292 self.cco.setDetectorPos(pupilPos=pupilPos, detectorPos=detectorPos, detector=detector, 

293 beam=beam) 

294 beamInfo = self.cco[beam] 

295 self.assertTrue(beamInfo.isOnDetector) 

296 self.assertEqual(beamInfo.detectorName, desiredDetector) 

297 self.assertPairsAlmostEqual(beamInfo.pupilPos, pupilPos, 

298 maxDiff=self.maxPupilPosErr) 

299 self.assertPairsAlmostEqual(beamInfo.detectorPos, detectorPos, 

300 maxDiff=self.maxDetectorPosErr) 

301 self.checkOrientation() 

302 

303 def testSetOffDetector(self): 

304 """Test a case where BeamInfo.isOnDetector should be False""" 

305 detector = self.cco.cameraGeom[0].getName() 

306 # Pick a position that is not covered by our sparse focal plane. 

307 detectorPos = (-20000, 0) 

308 self.cco.setDetectorPos(pupilPos=(0, 0), detectorPos=detectorPos, detector=detector, beam=0) 

309 for beamInfo in self.cco: 

310 self.assertFalse(beamInfo.isOnDetector) 

311 self.assertFalse(beamInfo.isVisible) 

312 

313 def testSetFocalPlanePos(self): 

314 maxErrMm = self.scc.pixelSizeMm * 0.0001 

315 for focalPlanePos, pupilPos, beam in itertools.product( 

316 ((0, 0), (500, 200), (25, 850)), 

317 ((0, 0), (0, 5000), (-5000, 0), (5000, -5000)), 

318 self.cco.beamNames, 

319 ): 

320 with self.subTest(focalPlanePos=focalPlanePos, pupilPos=pupilPos, beam=beam): 

321 self.cco.setFocalPlanePos(pupilPos=pupilPos, focalPlanePos=focalPlanePos, beam=beam) 

322 beamInfo = self.cco[beam] 

323 self.assertTrue(beamInfo.isOnFocalPlane) 

324 errMm = math.hypot(*np.subtract(beamInfo.focalPlanePos, focalPlanePos)) 

325 self.assertLess(errMm, maxErrMm) 

326 self.assertPairsAlmostEqual(beamInfo.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

327 

328 def testSetPupilFieldAngleZero(self): 

329 """Test setPupilFieldAngle for zero field angle 

330 and various points on the pupil. 

331 """ 

332 for pupilPos in ((0, 5000), (-5000, 0), (5000, -5000)): 

333 with self.subTest(pupilPos=pupilPos): 

334 self.cco.setPupilFieldAngle(pupilPos=pupilPos) 

335 

336 # The telescope should be pointing in the opposite direction 

337 # of the CBP. 

338 telDir = self.cco.telAzAltInternal.getVector() 

339 cbpDir = self.cco.cbpAzAltInternal.getVector() 

340 negativeCbpDir = -np.array(cbpDir, dtype=float) 

341 np.testing.assert_allclose(telDir, negativeCbpDir, atol=1e-15) 

342 

343 # Beam 0 should be pointed to the center of the pupil, 

344 # normal to the pupil, and land on the center of 

345 # the focal plane, which is also the center of detector D0. 

346 beamInfo0 = self.cco[0] 

347 self.assertEqual(beamInfo0.name, "beam0") 

348 self.assertPairsAlmostEqual(beamInfo0.holePos, (0, 0)) 

349 self.assertTrue(beamInfo0.isOnPupil) 

350 self.assertTrue(beamInfo0.isOnFocalPlane) 

351 self.assertTrue(beamInfo0.isOnDetector) 

352 self.assertTrue(beamInfo0.isVisible) 

353 self.assertPairsAlmostEqual(beamInfo0.focalPlanePos, (0, 0), maxDiff=self.maxFocalPlanePosErr) 

354 self.assertPairsAlmostEqual(beamInfo0.focalFieldAngle, (0, 0), 

355 maxDiff=self.maxFieldAngleErrRad) 

356 self.assertPairsAlmostEqual(beamInfo0.pupilFieldAngle, (0, 0), 

357 maxDiff=self.maxFieldAngleErrRad) 

358 self.assertPairsAlmostEqual(beamInfo0.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

359 self.assertEqual(beamInfo0.detectorName, "D0") 

360 bboxd = Box2D(self.cco.cameraGeom["D0"].getBBox()) 

361 detectorCtrPos = bboxd.getCenter() 

362 detector34Pos = bboxd.getMin() + bboxd.getDimensions()*0.75 

363 self.assertPairsAlmostEqual(beamInfo0.detectorPos, detectorCtrPos, 

364 maxDiff=self.maxDetectorPosErr) 

365 

366 # Beam 1 should land on detector D0, 

367 # 3/4 of the way from LL to UR. 

368 beamInfo1 = self.cco["beam1"] 

369 self.assertEqual(beamInfo1.name, "beam1") 

370 self.assertTrue(beamInfo1.isOnDetector) 

371 self.assertEqual(beamInfo1.detectorName, "D0") 

372 self.assertPairsAlmostEqual(beamInfo1.detectorPos, detector34Pos, 

373 maxDiff=self.maxDetectorPosErr) 

374 

375 # Beam 2 should land on the center of detector D1. 

376 beamInfo2 = self.cco["beam2"] 

377 self.assertEqual(beamInfo2.name, "beam2") 

378 self.assertTrue(beamInfo2.isOnDetector) 

379 self.assertEqual(beamInfo2.detectorName, "D1") 

380 self.assertPairsAlmostEqual(beamInfo2.detectorPos, detectorCtrPos, 

381 maxDiff=self.maxDetectorPosErr) 

382 

383 # Beam 3 should land on detector D1, 

384 # 3/4 of the way from LL to UR. 

385 beamInfo3 = self.cco["beam3"] 

386 self.assertEqual(beamInfo3.name, "beam3") 

387 self.assertTrue(beamInfo3.isOnDetector) 

388 self.assertEqual(beamInfo3.detectorName, "D1") 

389 self.assertPairsAlmostEqual(beamInfo3.detectorPos, detector34Pos, 

390 maxDiff=self.maxDetectorPosErr) 

391 

392 # Beam 4 should land on the center of detector D2. 

393 beamInfo4 = self.cco["beam4"] 

394 self.assertEqual(beamInfo4.name, "beam4") 

395 self.assertTrue(beamInfo4.isOnDetector) 

396 self.assertEqual(beamInfo4.detectorName, "D2") 

397 self.assertPairsAlmostEqual(beamInfo4.detectorPos, detectorCtrPos, 

398 maxDiff=self.maxDetectorPosErr) 

399 

400 # Beam 5 should land on detector D2, 

401 # 3/4 of the way from LL to UR. 

402 beamInfo5 = self.cco["beam5"] 

403 self.assertEqual(beamInfo5.name, "beam5") 

404 self.assertTrue(beamInfo5.isOnDetector) 

405 self.assertEqual(beamInfo5.detectorName, "D2") 

406 self.assertPairsAlmostEqual(beamInfo5.detectorPos, detector34Pos, 

407 maxDiff=self.maxDetectorPosErr) 

408 

409 def testSetPupilFieldAngle(self): 

410 for pupilFieldAngle, pupilPos, beam in itertools.product( 

411 ((0, 0), (0, 0.05), (-0.05, -0.03)), 

412 ((0, 0), (0, 5000), (-5000, 0), (5000, -5000)), 

413 self.cco.beamNames, 

414 ): 

415 with self.subTest(pupilFieldAngle=pupilFieldAngle, pupilPos=pupilPos, beam=beam): 

416 self.cco.setPupilFieldAngle(pupilPos=pupilPos, pupilFieldAngle=pupilFieldAngle, beam=beam) 

417 beamInfo = self.cco[beam] 

418 self.assertPairsAlmostEqual(beamInfo.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

419 self.assertPairsAlmostEqual(beamInfo.pupilFieldAngle, pupilFieldAngle, 

420 maxDiff=self.maxFieldAngleErrRad) 

421 self.checkOrientation() 

422 

423 def testAngleProperties(self): 

424 """Test cbpAzAltObserved, telAzAltObserved, telRotObserved 

425 and their Internal equivalents 

426 """ 

427 # Set non-trivial scale and offset for all axes; 

428 # azimuth and rotator scales must be ±1 to handle wrap correctly; 

429 # altitude scales should be nearly 1, 

430 # and altitude offsets should be small to avoid hitting limits. 

431 self.cco.config.cbpAzAltScale = (-1, 0.98) 

432 self.cco.config.cbpAzAltOffset = (33.2*degrees, -2.67*degrees) 

433 self.cco.config.telAzAltScale = (1, 0.95) 

434 self.cco.config.telAzAltOffset = (-31.5*degrees, 2.3*degrees) 

435 self.cco.config.telRotScale = -1 

436 self.cco.config.telRotOffset = 222.2*degrees 

437 for cbpAzAltObserved in ( 

438 SpherePoint(1, 2, degrees), 

439 SpherePoint(-45, 75.2, degrees), 

440 ): 

441 self.cco.cbpAzAltObserved = cbpAzAltObserved 

442 self.assertSpherePointsAlmostEqual(self.cco.cbpAzAltObserved, cbpAzAltObserved) 

443 

444 # Observed angle = internal angle * scale + offset. 

445 # Internal angle = (observed angle - offset) / scale. 

446 predictedCbpAzAltInternal = SpherePoint( 

447 *[(cbpAzAltObserved[i] - self.cco.config.cbpAzAltOffset[i])/self.cco.config.cbpAzAltScale[i] 

448 for i in range(2)]) 

449 self.assertSpherePointsAlmostEqual(self.cco.cbpAzAltInternal, predictedCbpAzAltInternal) 

450 

451 for telAzAltObserved in ( 

452 SpherePoint(-3, 5, degrees), 

453 SpherePoint(37, -.2, degrees), 

454 ): 

455 self.cco.telAzAltObserved = telAzAltObserved 

456 self.assertSpherePointsAlmostEqual(self.cco.telAzAltObserved, telAzAltObserved) 

457 predictedTelAzAltInternal = SpherePoint( 

458 *[(telAzAltObserved[i] - self.cco.config.telAzAltOffset[i])/self.cco.config.telAzAltScale[i] 

459 for i in range(2)]) 

460 self.assertSpherePointsAlmostEqual(self.cco.telAzAltInternal, predictedTelAzAltInternal) 

461 

462 for telRotObserved in (0*degrees, -32*degrees, 167*degrees): 

463 self.cco.telRotObserved = telRotObserved 

464 self.assertAnglesAlmostEqual(self.cco.telRotObserved, telRotObserved) 

465 predictedRotInternal = (telRotObserved - self.cco.config.telRotOffset)/self.cco.config.telRotScale 

466 self.assertAnglesAlmostEqual(self.cco.telRotInternal, predictedRotInternal) 

467 

468 def testInBounds(self): 

469 """Test the telInBounds and cbpInBounds properties. 

470 """ 

471 # Shrink the upper altitude limits so there's somewhere to go. 

472 self.cco.config.cbpAltitudeLimits = (-88*degrees, 88*degrees) 

473 self.cco.config.telAltitudeLimits = (5*degrees, 85*degrees) 

474 self.cco.setFocalPlanePos((0, 0)) 

475 self.assertTrue(self.cco.cbpInBounds) 

476 self.assertTrue(self.cco.telInBounds) 

477 originalCbpAzAltObserved = self.cco.cbpAzAltObserved 

478 cbpAltLim = self.cco.config.cbpAltitudeLimits 

479 for cbpAltObserved in cbpAltLim: 

480 self.cco.cbpAzAltObserved = SpherePoint(originalCbpAzAltObserved[0], cbpAltObserved) 

481 self.assertTrue(self.cco.cbpInBounds) 

482 self.assertTrue(self.cco.telInBounds) 

483 for badCbpAltObserved in ( 

484 cbpAltLim[0] - 1e-15*radians, 

485 cbpAltLim[0] - 2*degrees, 

486 cbpAltLim[1] + 1e-15*radians, 

487 cbpAltLim[1] + 2*degrees, 

488 ): 

489 self.cco.cbpAzAltObserved = SpherePoint(originalCbpAzAltObserved[0], badCbpAltObserved) 

490 self.assertFalse(self.cco.cbpInBounds) 

491 self.assertTrue(self.cco.telInBounds) 

492 self.cco.cbpAzAltObserved = originalCbpAzAltObserved 

493 

494 originalTelAzAltObserved = self.cco.telAzAltObserved 

495 telAltLim = self.cco.config.telAltitudeLimits 

496 for telAltObserved in telAltLim: 

497 self.cco.telAzAltObserved = SpherePoint(originalTelAzAltObserved[0], telAltObserved) 

498 self.assertTrue(self.cco.telInBounds) 

499 self.assertTrue(self.cco.cbpInBounds) 

500 for badTelAltObserved in ( 

501 telAltLim[0] - 1e-15*radians, 

502 telAltLim[0] - 2*degrees, 

503 telAltLim[1] + 1e-15*radians, 

504 telAltLim[1] + 2*degrees, 

505 ): 

506 self.cco.telAzAltObserved = SpherePoint(originalTelAzAltObserved[0], badTelAltObserved) 

507 self.assertFalse(self.cco.telInBounds) 

508 self.assertTrue(self.cco.cbpInBounds) 

509 

510 def testHolePositionsFlipX(self): 

511 for telFlipX, cbpFlipX in itertools.product((False, True), (False, True)): 

512 with self.subTest(telFlipX=telFlipX, cbpFlipX=cbpFlipX): 

513 scc = SampleCoordinateConverter(telFlipX=telFlipX, cbpFlipX=cbpFlipX) 

514 holePositions = [scc.maskInfo.getHolePos(name) for name in scc.maskInfo.holeNames] 

515 if not (telFlipX or cbpFlipX): 

516 unflippedHolePositions = holePositions[:] 

517 flippedHolePositions = [(-pos[0], pos[1]) for pos in unflippedHolePositions] 

518 elif cbpFlipX: 

519 self.assertPairListsAlmostEqual(holePositions, flippedHolePositions) 

520 else: 

521 self.assertPairListsAlmostEqual(holePositions, unflippedHolePositions) 

522 

523 def testSetPupilFieldAngleFlipX(self): 

524 """Test setPupilFieldAngle with varying flipX for telescope and CBP 

525 """ 

526 beam = 3 # pick a beam that is a bit off center 

527 for telFlipX, cbpFlipX in itertools.product((False, True), (False, True)): 

528 with self.subTest(telFlipX=telFlipX, cbpFlipX=cbpFlipX): 

529 # Flip pupilPos and fieldAngle so that everything else 

530 # is identical, e.g. all 3-D vectors. 

531 # This makes debugging easier if the test fails. 

532 unflippedPupilPos = (5000, -2500) 

533 unflippedPupilFieldAngle = (0.1, 0.12) 

534 pupilPos = coordUtils.getFlippedPos(unflippedPupilPos, flipX=telFlipX) 

535 pupilFieldAngle = coordUtils.getFlippedPos(unflippedPupilFieldAngle, flipX=telFlipX) 

536 

537 scc = SampleCoordinateConverter(telFlipX=telFlipX, cbpFlipX=cbpFlipX) 

538 cco = scc.coordinateConverter 

539 cco.setPupilFieldAngle(pupilPos=pupilPos, pupilFieldAngle=pupilFieldAngle, beam=beam) 

540 beamInfo = cco[beam] 

541 self.assertPairsAlmostEqual(beamInfo.pupilFieldAngle, pupilFieldAngle, 

542 maxDiff=self.maxFieldAngleErrRad) 

543 self.assertPairsAlmostEqual(beamInfo.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

544 

545 def testSetDetectorPosFlipX(self): 

546 """Test setDetectorPos with varying flipX for telescope and CBP 

547 """ 

548 pupilPos = (5000, -5000) 

549 detectorName = "D2" # pick a detector well away from the center of the focal plane 

550 detectorPos = (750, 250) 

551 beam = 3 # pick a beam that is a bit off center 

552 for telFlipX, cbpFlipX in itertools.product((False, True), (False, True)): 

553 with self.subTest(telFlipX=telFlipX, cbpFlipX=cbpFlipX): 

554 scc = SampleCoordinateConverter(telFlipX=telFlipX, cbpFlipX=cbpFlipX) 

555 cco = scc.coordinateConverter 

556 cco.setDetectorPos(pupilPos=pupilPos, detectorPos=detectorPos, detector=detectorName, 

557 beam=beam) 

558 beamInfo = cco[beam] 

559 self.assertTrue(beamInfo.isOnDetector) 

560 self.assertEqual(beamInfo.detectorName, detectorName) 

561 self.checkOrientation() 

562 self.assertPairsAlmostEqual(beamInfo.detectorPos, detectorPos, 

563 maxDiff=self.maxDetectorPosErr) 

564 self.assertPairsAlmostEqual(beamInfo.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) 

565 

566 def checkOrientation(self, maxDiff=50*arcseconds): 

567 """Check that the orientation of the focal plane is correct 

568 

569 The definition of correct orientation is that two points to the 

570 left and right of the CBP center (by a buried delta) 

571 line up in the focal plane. We'll just use +/-1 for our delta. 

572 """ 

573 ctrHolePos = Point2D(0, 0) 

574 dx = -1 if self.cco.config.cbpFlipX else 1 

575 holePos1 = ctrHolePos[0] + dx, ctrHolePos[1] 

576 holePos2 = ctrHolePos[0] + dx, ctrHolePos[1] 

577 beamInfo1 = self.cco.getBeamInfo(beam="virtual1", holePos=holePos1) 

578 beamInfo2 = self.cco.getBeamInfo(beam="virtual2", holePos=holePos2) 

579 deltaFocalPlane = np.subtract(beamInfo2.focalPlanePos, beamInfo1.focalPlanePos) 

580 orientError = -math.atan2(deltaFocalPlane[1], deltaFocalPlane[0])*radians 

581 self.assertAnglesAlmostEqual(orientError, 0*radians) 

582 

583 

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

585 pass 

586 

587 

588def setup_module(module): 

589 lsst.utils.tests.init() 

590 

591 

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

593 lsst.utils.tests.init() 

594 unittest.main()