Coverage for tests / test_cameraGeom.py: 12%

266 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-14 00:45 -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/>. 

21 

22import unittest 

23import os 

24 

25import astshim as ast 

26import numpy as np 

27 

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 

51 

52try: 

53 type(display) 

54except NameError: 

55 display = False 

56 

57testPath = os.path.abspath(os.path.dirname(__file__)) 

58 

59 

60class CameraGeomTestCase(lsst.utils.tests.TestCase): 

61 """A test case for camera geometry""" 

62 

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)] 

76 

77 def tearDown(self): 

78 del self.lsstCamWrapper 

79 del self.scCamWrapper 

80 del self.scCamFlippedWrapper 

81 del self.cameraList 

82 del self.assemblyList 

83 

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)) 

99 

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) 

107 

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) 

115 

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) 

123 

124 def testTransformSlalib(self): 

125 """Test Camera.transform against data computed using SLALIB 

126 

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), ] 

150 

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])) 

157 

158 if camera.getFocalPlaneParity(): 

159 fieldGivenPos.x *= -1 

160 

161 fieldAngleToFocalPlane = camera.getTransform(FIELD_ANGLE, FOCAL_PLANE) 

162 fpComputedPos = fieldAngleToFocalPlane.applyForward(fieldGivenPos) 

163 self.assertPairsAlmostEqual(fpComputedPos, fpGivenPos) 

164 

165 focalPlaneToFieldAngle = camera.getTransform(FOCAL_PLANE, FIELD_ANGLE) 

166 fieldComputedPos = focalPlaneToFieldAngle.applyForward(fpGivenPos) 

167 self.assertPairsAlmostEqual(fieldComputedPos, fieldGivenPos) 

168 

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] 

178 

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) 

189 

190 posFieldAngle3 = camera.transform(posPixels, pixSys, FIELD_ANGLE) 

191 self.assertPairsAlmostEqual(posFieldAngle, posFieldAngle3) 

192 

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) 

202 

203 posPixelsRoundTrip2 = camera.transform(intermedPos, intermedSys, pixelSys) 

204 self.assertPairsAlmostEqual(posPixels, posPixelsRoundTrip2) 

205 

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 

215 

216 otherDet = detList[0] 

217 self.assertNotEqual(otherDet, det) 

218 otherCoordPixSys = otherDet.makeCameraSys(PIXELS) 

219 

220 pixelsToOtherPixels = camera.getTransform(pixCoordSys, otherCoordPixSys) 

221 otherPixels = pixelsToOtherPixels.applyForward(pixOffDet) 

222 with self.assertRaises(AssertionError): 

223 self.assertPairsAlmostEqual(otherPixels, pixOffDet) 

224 

225 # convert back 

226 otherPixelsToPixels = camera.getTransform(otherCoordPixSys, pixCoordSys) 

227 pixOffDetRoundTrip = otherPixelsToPixels.applyForward(otherPixels) 

228 self.assertPairsAlmostEqual(pixOffDet, pixOffDetRoundTrip) 

229 self.assertEqual(numOffUsable, 5) 

230 

231 def testAACustomDetectorTransform(self): 

232 """Test that user-set detector to focal plane transforms are not 

233 overriden. 

234 """ 

235 cameraBuilder = Camera.Builder("CameraWithCustomTransforms") 

236 

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()) 

242 

243 customMap = ast.ZoomMap(2, 1.5) 

244 transform = TransformPoint2ToPoint2(customMap) 

245 detectorBuilder.setTransformFromPixelsTo(FOCAL_PLANE, transform) 

246 

247 # Detector with default map: 

248 detectorBuilder = cameraBuilder.add("Det1", 1) 

249 detectorBuilder.setPixelSize(pixelSize) 

250 detectorBuilder.setOrientation(Orientation()) 

251 

252 camera = cameraBuilder.finish() 

253 

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) 

257 

258 self.assertEqual(testPointFPDet0.x, 1.5) 

259 self.assertNotEqual(testPointFPDet0.x, testPointFPDet1.x) 

260 

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) 

278 

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()) 

288 

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) 

307 

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) 

351 

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() 

371 

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")) 

396 

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) 

408 

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) 

418 

419 

420class TestMemory(lsst.utils.tests.MemoryTestCase): 

421 pass 

422 

423 

424def setup_module(module): 

425 lsst.utils.tests.init() 

426 

427 

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()