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