Coverage for tests/test_coordinateConverter.py: 9%
358 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-02 02:19 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-02 02:19 -0700
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/>.
22import itertools
23import math
24import unittest
26import numpy as np
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
36# set True to record and report numeric error in convertVectorFromPupilToBase
37# and in the iterative component of setFocalFieldAngle
38ReportRecordedErrors = True
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()
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))
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
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)
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)
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)
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)
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)
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))
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)
200 self.assertAnglesAlmostEqual(self.cco.telRotInternal, 0*degrees)
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)
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)
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)
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)
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)
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)
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()
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()
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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()
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)
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)
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)
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)
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
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)
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)
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)
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)
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)
566 def checkOrientation(self, maxDiff=50*arcseconds):
567 """Check that the orientation of the focal plane is correct
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)
584class MemoryTester(lsst.utils.tests.MemoryTestCase):
585 pass
588def setup_module(module):
589 lsst.utils.tests.init()
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()