Coverage for tests/test_cameraGeom.py: 11%
241 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-20 00:40 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-20 00:40 -0700
1# This file is part of afw.
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 unittest
23import os
25import numpy as np
27import lsst.utils.tests
28import lsst.pex.exceptions as pexExcept
29import lsst.geom
30import lsst.afw.image as afwImage
31import lsst.afw.display as afwDisplay
32from lsst.afw.cameraGeom import (
33 AmplifierIsolator,
34 assembleAmplifierImage,
35 assembleAmplifierRawImage,
36 Camera,
37 CameraSys,
38 CameraSysPrefix,
39 Detector,
40 DetectorCollection,
41 FIELD_ANGLE,
42 FOCAL_PLANE,
43 makeUpdatedDetector,
44 PIXELS,
45)
46import lsst.afw.cameraGeom.testUtils as testUtils
47import lsst.afw.cameraGeom.utils as cameraGeomUtils
49try:
50 type(display)
51except NameError:
52 display = False
54testPath = os.path.abspath(os.path.dirname(__file__))
57class CameraGeomTestCase(lsst.utils.tests.TestCase):
58 """A test case for camera geometry"""
60 def setUp(self):
61 self.lsstCamWrapper = testUtils.CameraWrapper(isLsstLike=True)
62 self.scCamWrapper = testUtils.CameraWrapper(isLsstLike=False)
63 self.cameraList = (self.lsstCamWrapper, self.scCamWrapper)
64 self.assemblyList = {}
65 self.assemblyList[self.lsstCamWrapper.camera.getName()] =\
66 [afwImage.ImageU(os.path.join(testPath, 'test_amp.fits.gz'))
67 for i in range(8)]
68 self.assemblyList[self.scCamWrapper.camera.getName()] =\
69 [afwImage.ImageU(os.path.join(testPath, 'test.fits.gz'), allowUnsafe=True)]
71 def tearDown(self):
72 del self.lsstCamWrapper
73 del self.scCamWrapper
74 del self.cameraList
75 del self.assemblyList
77 def testConstructor(self):
78 for cw in self.cameraList:
79 self.assertIsInstance(cw.camera, Camera)
80 self.assertEqual(cw.nDetectors, len(cw.camera))
81 self.assertEqual(cw.nDetectors, len(cw.ampDataDict))
82 self.assertEqual(sorted(cw.detectorNameList),
83 sorted(cw.camera.getNameIter()))
84 self.assertEqual(sorted(cw.detectorIdList),
85 sorted(cw.camera.getIdIter()))
86 for det in cw.camera:
87 self.assertIsInstance(det, Detector)
88 self.assertEqual(
89 cw.ampDataDict[det.getName()]['namps'], len(det))
90 idList = [det.getId() for det in cw.camera]
91 self.assertEqual(idList, sorted(idList))
93 def testCameraSysRepr(self):
94 """Test CameraSys.__repr__ and CameraSysPrefix.__repr__
95 """
96 for sysName in ("FocalPlane", "FieldAngle", "Pixels", "foo"):
97 cameraSys = CameraSys(sysName)
98 predRepr = f"CameraSys({sysName})"
99 self.assertEqual(repr(cameraSys), predRepr)
101 cameraSysPrefix = CameraSysPrefix(sysName)
102 predCSPRepr = f"CameraSysPrefix({sysName})"
103 self.assertEqual(repr(cameraSysPrefix), predCSPRepr)
104 for detectorName in ("Detector 1", "bar"):
105 cameraSys2 = CameraSys(sysName, detectorName)
106 predRepr2 = f"CameraSys({sysName}, {detectorName})"
107 self.assertEqual(repr(cameraSys2), predRepr2)
109 def testAccessor(self):
110 for cw in self.cameraList:
111 camera = cw.camera
112 for name in cw.detectorNameList:
113 self.assertIsInstance(camera[name], Detector)
114 for detId in cw.detectorIdList:
115 self.assertIsInstance(camera[detId], Detector)
117 def testTransformSlalib(self):
118 """Test Camera.transform against data computed using SLALIB
120 These test data come from SLALIB using SLA_PCD with 0.925 and
121 a plate scale of 20 arcsec/mm
122 """
123 testData = [(-1.84000000, 1.04000000, -331.61689069, 187.43563387),
124 (-1.64000000, 0.12000000, -295.42491556, 21.61645724),
125 (-1.44000000, -0.80000000, -259.39818797, -144.11010443),
126 (-1.24000000, -1.72000000, -223.48275934, -309.99221457),
127 (-1.08000000, 1.36000000, -194.56520533, 245.00803635),
128 (-0.88000000, 0.44000000, -158.44320430, 79.22160215),
129 (-0.68000000, -0.48000000, -122.42389383, -86.41686623),
130 (-0.48000000, -1.40000000, -86.45332534, -252.15553224),
131 (-0.32000000, 1.68000000, -57.64746955, 302.64921514),
132 (-0.12000000, 0.76000000, -21.60360306, 136.82281940),
133 (0.08000000, -0.16000000, 14.40012984, -28.80025968),
134 (0.28000000, -1.08000000, 50.41767773, -194.46818554),
135 (0.48000000, -2.00000000, 86.50298919, -360.42912163),
136 (0.64000000, 1.08000000, 115.25115701, 194.48632746),
137 (0.84000000, 0.16000000, 151.23115189, 28.80593369),
138 (1.04000000, -0.76000000, 187.28751874, -136.86395600),
139 (1.24000000, -1.68000000, 223.47420612, -302.77150507),
140 (1.40000000, 1.40000000, 252.27834478, 252.27834478),
141 (1.60000000, 0.48000000, 288.22644118, 86.46793236),
142 (1.80000000, -0.44000000, 324.31346653, -79.27662515), ]
144 for cw in self.cameraList:
145 camera = cw.camera
146 for point in testData:
147 fpGivenPos = lsst.geom.Point2D(point[2], point[3])
148 fieldGivenPos = lsst.geom.Point2D(
149 lsst.geom.degToRad(point[0]), lsst.geom.degToRad(point[1]))
151 fieldAngleToFocalPlane = camera.getTransform(FIELD_ANGLE, FOCAL_PLANE)
152 fpComputedPos = fieldAngleToFocalPlane.applyForward(fieldGivenPos)
153 self.assertPairsAlmostEqual(fpComputedPos, fpGivenPos)
155 focalPlaneToFieldAngle = camera.getTransform(FOCAL_PLANE, FIELD_ANGLE)
156 fieldComputedPos = focalPlaneToFieldAngle.applyForward(fpGivenPos)
157 self.assertPairsAlmostEqual(fieldComputedPos, fieldGivenPos)
159 def testTransformDet(self):
160 """Test Camera.getTransform with detector-based coordinate systems (PIXELS)
161 """
162 for cw in self.cameraList:
163 numOffUsable = 0 # number of points off one detector but on another
164 camera = cw.camera
165 detNameList = list(camera.getNameIter())
166 for detName in detNameList:
167 det = camera[detName]
169 # test transforms using an arbitrary point on the detector
170 posPixels = lsst.geom.Point2D(10, 10)
171 pixSys = det.makeCameraSys(PIXELS)
172 pixelsToFocalPlane = camera.getTransform(pixSys, FOCAL_PLANE)
173 pixelsToFieldAngle = camera.getTransform(pixSys, FIELD_ANGLE)
174 focalPlaneToFieldAngle = camera.getTransform(FOCAL_PLANE, FIELD_ANGLE)
175 posFocalPlane = pixelsToFocalPlane.applyForward(posPixels)
176 posFieldAngle = pixelsToFieldAngle.applyForward(posPixels)
177 posFieldAngle2 = focalPlaneToFieldAngle.applyForward(posFocalPlane)
178 self.assertPairsAlmostEqual(posFieldAngle, posFieldAngle2)
180 posFieldAngle3 = camera.transform(posPixels, pixSys, FIELD_ANGLE)
181 self.assertPairsAlmostEqual(posFieldAngle, posFieldAngle3)
183 for intermedPos, intermedSys in (
184 (posPixels, pixSys),
185 (posFocalPlane, FOCAL_PLANE),
186 (posFieldAngle, FIELD_ANGLE),
187 ):
188 pixelSys = det.makeCameraSys(PIXELS)
189 intermedSysToPixels = camera.getTransform(intermedSys, pixelSys)
190 posPixelsRoundTrip = intermedSysToPixels.applyForward(intermedPos)
191 self.assertPairsAlmostEqual(posPixels, posPixelsRoundTrip)
193 posPixelsRoundTrip2 = camera.transform(intermedPos, intermedSys, pixelSys)
194 self.assertPairsAlmostEqual(posPixels, posPixelsRoundTrip2)
196 # Test finding detectors for a point off this detector.
197 # The point off the detector may be on one other detector,
198 # depending if the detector has neighbor on the correct edge.
199 pixOffDet = lsst.geom.Point2D(0, -10)
200 pixCoordSys = det.makeCameraSys(PIXELS)
201 detList = camera.findDetectors(pixOffDet, pixCoordSys)
202 self.assertIn(len(detList), (0, 1))
203 if len(detList) == 1:
204 numOffUsable += 1
206 otherDet = detList[0]
207 self.assertNotEqual(otherDet, det)
208 otherCoordPixSys = otherDet.makeCameraSys(PIXELS)
210 pixelsToOtherPixels = camera.getTransform(pixCoordSys, otherCoordPixSys)
211 otherPixels = pixelsToOtherPixels.applyForward(pixOffDet)
212 with self.assertRaises(AssertionError):
213 self.assertPairsAlmostEqual(otherPixels, pixOffDet)
215 # convert back
216 otherPixelsToPixels = camera.getTransform(otherCoordPixSys, pixCoordSys)
217 pixOffDetRoundTrip = otherPixelsToPixels.applyForward(otherPixels)
218 self.assertPairsAlmostEqual(pixOffDet, pixOffDetRoundTrip)
219 self.assertEqual(numOffUsable, 5)
221 def testFindDetectors(self):
222 for cw in self.cameraList:
223 detCtrFocalPlaneList = []
224 for det in cw.camera:
225 # This currently assumes there is only one detector at the center
226 # position of any detector. That is not enforced and multiple detectors
227 # at a given FIELD_ANGLE position is supported. Change this if the default
228 # camera changes.
229 detCtrFocalPlane = det.getCenter(FOCAL_PLANE)
230 detCtrFocalPlaneList.append(detCtrFocalPlane)
231 detList = cw.camera.findDetectors(detCtrFocalPlane, FOCAL_PLANE)
232 self.assertEqual(len(detList), 1)
233 self.assertEqual(det.getName(), detList[0].getName())
234 detList = cw.camera.findDetectorsList(detCtrFocalPlaneList, FOCAL_PLANE)
235 self.assertEqual(len(cw.camera), len(detList))
236 for dets in detList:
237 self.assertEqual(len(dets), 1)
239 def testFpBbox(self):
240 for cw in self.cameraList:
241 camera = cw.camera
242 bbox = lsst.geom.Box2D()
243 for name in cw.detectorNameList:
244 for corner in camera[name].getCorners(FOCAL_PLANE):
245 bbox.include(corner)
246 self.assertEqual(bbox.getMin(), camera.getFpBBox().getMin())
247 self.assertEqual(bbox.getMax(), camera.getFpBBox().getMax())
249 def testLinearity(self):
250 """Test if we can set/get Linearity parameters"""
251 for cw in self.cameraList:
252 camera = cw.camera
253 for det in camera:
254 for amp in det:
255 self.assertEqual(cw.ampDataDict[det.getName()]['linInfo'][amp.getName()]['linthresh'],
256 amp.getLinearityThreshold())
257 self.assertEqual(cw.ampDataDict[det.getName()]['linInfo'][amp.getName()]['linmax'],
258 amp.getLinearityMaximum())
259 self.assertEqual(cw.ampDataDict[det.getName()]['linInfo'][amp.getName()]['linunits'],
260 amp.getLinearityUnits())
261 self.assertEqual(cw.ampDataDict[det.getName()]['linInfo'][amp.getName()]['lintype'],
262 amp.getLinearityType())
263 for c1, c2 in zip(cw.ampDataDict[det.getName()]['linInfo'][amp.getName()]['lincoeffs'],
264 amp.getLinearityCoeffs()):
265 if np.isfinite(c1) and np.isfinite(c2):
266 self.assertEqual(c1, c2)
268 def testAssembly(self):
269 ccdNames = ('R:0,0 S:1,0', 'R:0,0 S:0,1')
270 detectorImageMap = {True: afwImage.ImageU(os.path.join(testPath, 'test_comp_trimmed.fits.gz'),
271 allowUnsafe=True),
272 False: afwImage.ImageU(os.path.join(testPath, 'test_comp.fits.gz'),
273 allowUnsafe=True)}
274 for cw in self.cameraList:
275 camera = cw.camera
276 imList = self.assemblyList[camera.getName()]
277 for ccdName in ccdNames:
278 det = camera[ccdName]
279 if len(imList) == 1:
280 # There's one test image because it's the same for all
281 # amplifiers, not because there's only one amplifier.
282 imList *= len(det)
283 # Test going from possibly-separate amp images to detector
284 # images.
285 for trim, assemble in ((False, assembleAmplifierRawImage), (True, assembleAmplifierImage)):
286 if not trim:
287 outBbox = cameraGeomUtils.calcRawCcdBBox(det)
288 else:
289 outBbox = det.getBBox()
290 outImage = afwImage.ImageU(outBbox)
291 for amp, im in zip(det, imList):
292 assemble(outImage, im, amp)
293 self.assertImagesEqual(outImage, detectorImageMap[trim])
294 # Test going from detector images back to single-amplifier
295 # images.
296 detector_exposure = afwImage.ExposureU(afwImage.MaskedImageU(detectorImageMap[trim]))
297 detector_exposure.setDetector(makeUpdatedDetector(det))
298 for amp, im in zip(det, imList):
299 amp_exposure = AmplifierIsolator.apply(detector_exposure, amp)
300 self.assertEqual(len(amp_exposure.getDetector()), 1)
301 self.assertEqual(amp_exposure.getDetector().getBBox(), amp.getBBox())
302 self.assertAmplifiersEqual(amp, amp_exposure.getDetector()[0])
303 if not trim:
304 self.assertEqual(cameraGeomUtils.calcRawCcdBBox(amp_exposure.getDetector()),
305 amp_exposure.getBBox())
306 self.assertImagesEqual(im[amp.getRawBBox()], amp_exposure.image)
307 else:
308 self.assertEqual(amp_exposure.getDetector().getBBox(),
309 amp_exposure.getBBox())
310 self.assertImagesEqual(im[amp.getRawDataBBox()], amp_exposure.image)
312 @unittest.skipIf(not display, "display variable not set; skipping cameraGeomUtils test")
313 def testCameraGeomUtils(self):
314 for cw in self.cameraList:
315 camera = cw.camera
316 disp = afwDisplay.Display()
317 cameraGeomUtils.showCamera(camera, display=disp)
318 disp.incrDefaultFrame()
319 for det in (camera[10], camera[20]):
320 cameraGeomUtils.showCcd(det, inCameraCoords=False)
321 disp.incrDefaultFrame()
322 cameraGeomUtils.showCcd(det, inCameraCoords=True)
323 disp.incrDefaultFrame()
324 cameraGeomUtils.showCcd(det, inCameraCoords=False)
325 disp.incrDefaultFrame()
326 cameraGeomUtils.showCcd(det, inCameraCoords=True)
327 disp.incrDefaultFrame()
328 for amp in det:
329 cameraGeomUtils.showAmp(amp, display=disp, imageFactory=afwImage.ImageF)
330 disp.incrDefaultFrame()
332 def testCameraRaises(self):
333 for cw in self.cameraList:
334 camera = cw.camera
335 point = lsst.geom.Point2D(0, 0)
336 # non-existant source camera system
337 with self.assertRaises(pexExcept.InvalidParameterError):
338 camera.getTransform(CameraSys("badSystem"), FOCAL_PLANE)
339 with self.assertRaises(pexExcept.InvalidParameterError):
340 camera.transform(point, CameraSys("badSystem"), FOCAL_PLANE)
341 # non-existant destination camera system
342 with self.assertRaises(pexExcept.InvalidParameterError):
343 camera.getTransform(FOCAL_PLANE, CameraSys("badSystem"))
344 with self.assertRaises(pexExcept.InvalidParameterError):
345 camera.transform(point, FOCAL_PLANE, CameraSys("badSystem"))
346 # non-existent source detector
347 with self.assertRaises(pexExcept.InvalidParameterError):
348 camera.getTransform(CameraSys("pixels", "invalid"), FOCAL_PLANE)
349 with self.assertRaises(pexExcept.InvalidParameterError):
350 camera.transform(point, CameraSys("pixels", "invalid"), FOCAL_PLANE)
351 # non-existent destination detector
352 with self.assertRaises(pexExcept.InvalidParameterError):
353 camera.getTransform(FOCAL_PLANE, CameraSys("pixels", "invalid"))
354 with self.assertRaises(pexExcept.InvalidParameterError):
355 camera.transform(point, FOCAL_PLANE, CameraSys("pixels", "invalid"))
357 def testDetectorCollectionPersistence(self):
358 """Test that we can round-trip a DetectorCollection through FITS I/O.
359 """
360 for wrapper in self.cameraList:
361 camera = wrapper.camera
362 detectors = list(camera)
363 collectionIn = DetectorCollection(detectors)
364 with lsst.utils.tests.getTempFilePath(".fits") as filename:
365 collectionIn.writeFits(filename)
366 collectionOut = DetectorCollection.readFits(filename)
367 self.assertDetectorCollectionsEqual(collectionIn, collectionOut)
369 def testCameraPersistence(self):
370 """Test that we can round-trip a Camera through FITS I/O.
371 """
372 for wrapper in self.cameraList:
373 cameraIn = wrapper.camera
374 with lsst.utils.tests.getTempFilePath(".fits") as filename:
375 cameraIn.writeFits(filename)
376 cameraOut = Camera.readFits(filename)
377 self.assertCamerasEqual(cameraIn, cameraOut)
380class TestMemory(lsst.utils.tests.MemoryTestCase):
381 pass
384def setup_module(module):
385 lsst.utils.tests.init()
388if __name__ == "__main__": 388 ↛ 389line 388 didn't jump to line 389, because the condition on line 388 was never true
389 lsst.utils.tests.init()
390 unittest.main()