Coverage for python/lsst/afw/cameraGeom/testUtils.py: 11%

305 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-14 02:44 -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 

22__all__ = ["DetectorWrapper", "CameraWrapper"] 

23 

24import os 

25 

26import numpy as np 

27 

28import lsst.utils 

29import lsst.geom 

30import lsst.afw.geom as afwGeom 

31from lsst.utils.tests import inTestCase 

32from ._cameraGeom import CameraSys, PIXELS, TAN_PIXELS, FIELD_ANGLE, FOCAL_PLANE, ACTUAL_PIXELS, Orientation 

33from ._cameraGeom import Amplifier, ReadoutCorner 

34from ._camera import Camera 

35from ._cameraGeom import DetectorType 

36from .cameraConfig import DetectorConfig, CameraConfig 

37from ._cameraFactory import makeCameraFromAmpLists 

38from ._makePixelToTanPixel import makePixelToTanPixel 

39from ._transformConfig import TransformMapConfig 

40 

41 

42class DetectorWrapper: 

43 """A Detector and the data used to construct it 

44 

45 Intended for use with unit tests, thus saves a copy of all input parameters. 

46 Does not support setting details of amplifiers. 

47 

48 Parameters 

49 ---------- 

50 name : `str` (optional) 

51 Detector name. 

52 id : `int` (optional) 

53 Detector ID. 

54 detType : `lsst.afw.cameraGeom.DetectorType` (optional) 

55 Detector type. 

56 serial : `str` (optional) 

57 Serial "number". 

58 bbox : `lsst.geom.Box2I` (optional) 

59 Bounding box; defaults to (0, 0), (1024x1024). 

60 numAmps : `int` (optional) 

61 Number of amplifiers. 

62 pixelSize : `lsst.geom.Point2D` (optional) 

63 Pixel size (mm). 

64 ampExtent : `lsst.geom.Extent2I` (optional) 

65 Dimensions of amplifier image bbox. 

66 orientation : `lsst.afw.cameraGeom.Orientation` (optional) 

67 Orientation of CCC in focal plane. 

68 plateScale : `float` (optional) 

69 Plate scale in arcsec/mm; 20.0 is for LSST. 

70 radialDistortion : `float` (optional) 

71 Radial distortion, in mm/rad^2. 

72 The r^3 coefficient of the radial distortion polynomial 

73 that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm; 

74 0.925 is the value Dave Monet measured for lsstSim data 

75 crosstalk : `iterable` (optional) 

76 Crosstalk coefficient matrix. If None, then no crosstalk correction 

77 can be performed. 

78 modFunc : `callable` (optional) 

79 A function that can modify attributes just before constructing the 

80 detector; modFunc receives one argument: a DetectorWrapper with all 

81 attributes except detector set. 

82 physicalType : `str` (optional) 

83 The physical type of the device, e.g. CCD, E2V, HgCdTe 

84 """ 

85 

86 def __init__(self, 

87 name="detector 1", 

88 id=1, 

89 detType=DetectorType.SCIENCE, 

90 serial="xkcd722", 

91 bbox=None, # do not use mutable objects as defaults 

92 numAmps=3, 

93 pixelSize=(0.02, 0.02), 

94 ampExtent=(5, 6), 

95 orientation=Orientation(), 

96 plateScale=20.0, 

97 radialDistortion=0.925, 

98 crosstalk=None, 

99 modFunc=None, 

100 physicalType="CCD", 

101 cameraBuilder=None 

102 ): 

103 # note that (0., 0.) for the reference position is the center of the 

104 # first pixel 

105 self.name = name 

106 self.id = int(id) 

107 self.type = detType 

108 self.serial = serial 

109 if bbox is None: 

110 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(1024, 1048)) 

111 self.bbox = bbox 

112 self.pixelSize = lsst.geom.Extent2D(*pixelSize) 

113 self.ampExtent = lsst.geom.Extent2I(*ampExtent) 

114 self.plateScale = float(plateScale) 

115 self.orientation = orientation 

116 self.radialDistortion = float(radialDistortion) 

117 

118 # compute TAN_PIXELS transform 

119 pScaleRad = lsst.geom.arcsecToRad(self.plateScale) 

120 radialDistortCoeffs = [0.0, 1.0/pScaleRad, 

121 0.0, self.radialDistortion/pScaleRad] 

122 focalPlaneToField = afwGeom.makeRadialTransform(radialDistortCoeffs) 

123 pixelToTanPixel = makePixelToTanPixel( 

124 bbox=self.bbox, 

125 orientation=self.orientation, 

126 focalPlaneToField=focalPlaneToField, 

127 pixelSizeMm=self.pixelSize, 

128 ) 

129 tanPixelSys = CameraSys(TAN_PIXELS, self.name) 

130 actualPixelSys = CameraSys(ACTUAL_PIXELS, self.name) 

131 self.transMap = { 

132 FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize), 

133 tanPixelSys: pixelToTanPixel, 

134 actualPixelSys: afwGeom.makeRadialTransform([0, 0.95, 0.01]), 

135 } 

136 if crosstalk is None: 

137 crosstalk = [[0.0 for _ in range(numAmps)] for _ in range(numAmps)] 

138 self.crosstalk = crosstalk 

139 self.physicalType = physicalType 

140 if cameraBuilder is None: 

141 cameraBuilder = Camera.Builder("CameraForDetectorWrapper") 

142 self.ampList = [] 

143 for i in range(numAmps): 

144 ampBuilder = Amplifier.Builder() 

145 ampName = f"amp {i + 1}" 

146 ampBuilder.setName(ampName) 

147 ampBuilder.setBBox(lsst.geom.Box2I(lsst.geom.Point2I(-1, 1), self.ampExtent)) 

148 ampBuilder.setGain(1.71234e3) 

149 ampBuilder.setReadNoise(0.521237e2) 

150 ampBuilder.setReadoutCorner(ReadoutCorner.LL) 

151 self.ampList.append(ampBuilder) 

152 if modFunc: 

153 modFunc(self) 

154 detectorBuilder = cameraBuilder.add(self.name, self.id) 

155 detectorBuilder.setType(self.type) 

156 detectorBuilder.setSerial(self.serial) 

157 detectorBuilder.setPhysicalType(self.physicalType) 

158 detectorBuilder.setBBox(self.bbox) 

159 detectorBuilder.setOrientation(self.orientation) 

160 detectorBuilder.setPixelSize(self.pixelSize) 

161 detectorBuilder.setTransformFromPixelsTo(tanPixelSys, self.transMap[tanPixelSys]) 

162 detectorBuilder.setTransformFromPixelsTo(actualPixelSys, self.transMap[actualPixelSys]) 

163 detectorBuilder.setCrosstalk(np.array(self.crosstalk, dtype=np.float32)) 

164 for ampBuilder in self.ampList: 

165 detectorBuilder.append(ampBuilder) 

166 camera = cameraBuilder.finish() 

167 self.detector = camera[self.name] 

168 

169 

170class CameraWrapper: 

171 """A simple Camera and the data used to construct it 

172 

173 Intended for use with unit tests, thus saves some interesting information. 

174 

175 Parameters 

176 ---------- 

177 plateScale : `float` 

178 Plate scale in arcsec/mm; 20.0 is for LSST. 

179 radialDistortion : `float` 

180 Radial distortion, in mm/rad^2. 

181 The r^3 coefficient of the radial distortion polynomial 

182 that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm; 

183 0.925 is the value Dave Monet measured for lsstSim data. 

184 isLsstLike : `bool`. 

185 Make repository products with one raw image per amplifier (True) 

186 or with one raw image per detector (False). 

187 """ 

188 

189 def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False): 

190 afwDir = lsst.utils.getPackageDir("afw") 

191 self._afwTestDataDir = os.path.join(afwDir, "python", "lsst", "afw", 

192 "cameraGeom", "testData") 

193 

194 # Info to store for unit tests 

195 self.plateScale = float(plateScale) 

196 self.radialDistortion = float(radialDistortion) 

197 self.detectorNameList = [] 

198 self.detectorIdList = [] 

199 self.ampDataDict = {} # ampData[Dict]: raw dictionaries of test data fields 

200 

201 # ampList[Dict]: actual cameraGeom.Amplifier objects 

202 self.camConfig, self.ampListDict = self.makeTestRepositoryItems( 

203 isLsstLike) 

204 self.camera = makeCameraFromAmpLists( 

205 self.camConfig, self.ampListDict) 

206 

207 @property 

208 def nDetectors(self): 

209 """Return the number of detectors""" 

210 return len(self.detectorNameList) 

211 

212 def makeDetectorConfigs(self, detFile): 

213 """Construct a list of DetectorConfig, one per detector 

214 """ 

215 detectors = [] 

216 self.detectorNameList = [] 

217 self.detectorIdList = [] 

218 with open(detFile) as fh: 

219 names = fh.readline().rstrip().lstrip("#").split("|") 

220 for line in fh: 

221 els = line.rstrip().split("|") 

222 detectorProps = dict([(name, el) 

223 for name, el in zip(names, els)]) 

224 detectors.append(detectorProps) 

225 detectorConfigs = [] 

226 for i, detector in enumerate(detectors): 

227 detectorId = (i + 1) * 10 # to avoid simple 0, 1, 2... 

228 detectorName = detector['name'] 

229 detConfig = DetectorConfig() 

230 detConfig.name = detectorName 

231 detConfig.id = detectorId 

232 detConfig.bbox_x0 = 0 

233 detConfig.bbox_y0 = 0 

234 detConfig.bbox_x1 = int(detector['npix_x']) - 1 

235 detConfig.bbox_y1 = int(detector['npix_y']) - 1 

236 detConfig.serial = str(detector['serial']) 

237 detConfig.detectorType = int(detector['detectorType']) 

238 detConfig.offset_x = float(detector['x']) 

239 detConfig.offset_y = float(detector['y']) 

240 detConfig.refpos_x = float(detector['refPixPos_x']) 

241 detConfig.refpos_y = float(detector['refPixPos_y']) 

242 detConfig.yawDeg = float(detector['yaw']) 

243 detConfig.pitchDeg = float(detector['pitch']) 

244 detConfig.rollDeg = float(detector['roll']) 

245 detConfig.pixelSize_x = float(detector['pixelSize']) 

246 detConfig.pixelSize_y = float(detector['pixelSize']) 

247 detConfig.transposeDetector = False 

248 detConfig.transformDict.nativeSys = PIXELS.getSysName() 

249 detectorConfigs.append(detConfig) 

250 self.detectorNameList.append(detectorName) 

251 self.detectorIdList.append(detectorId) 

252 return detectorConfigs 

253 

254 def makeAmpLists(self, ampFile, isLsstLike=False): 

255 """Construct a dict of list of Amplifer, one list per detector. 

256 

257 Parameters 

258 ---------- 

259 ampFile : `str` 

260 Path to amplifier data file. 

261 isLsstLike : `bool` 

262 If True then there is one raw image per amplifier; 

263 if False then there is one raw image per detector. 

264 """ 

265 readoutMap = { 

266 'LL': ReadoutCorner.LL, 

267 'LR': ReadoutCorner.LR, 

268 'UR': ReadoutCorner.UR, 

269 'UL': ReadoutCorner.UL, 

270 } 

271 ampDataList = [] 

272 with open(ampFile) as fh: 

273 names = fh.readline().rstrip().lstrip("#").split("|") 

274 for line in fh: 

275 els = line.rstrip().split("|") 

276 ampProps = dict([(name, el) for name, el in zip(names, els)]) 

277 ampDataList.append(ampProps) 

278 ampListDict = {} 

279 self.ampDataDict = {} 

280 for ampData in ampDataList: 

281 if ampData['ccd_name'] in ampListDict: 

282 ampList = ampListDict[ampData['ccd_name']] 

283 self.ampDataDict[ampData['ccd_name']]['namps'] += 1 

284 else: 

285 ampList = [] 

286 ampListDict[ampData['ccd_name']] = ampList 

287 self.ampDataDict[ampData['ccd_name']] = {'namps': 1, 'linInfo': {}} 

288 builder = Amplifier.Builder() 

289 bbox = lsst.geom.Box2I(lsst.geom.Point2I(int(ampData['trimmed_xmin']), 

290 int(ampData['trimmed_ymin'])), 

291 lsst.geom.Point2I(int(ampData['trimmed_xmax']), 

292 int(ampData['trimmed_ymax']))) 

293 rawBbox = lsst.geom.Box2I(lsst.geom.Point2I(int(ampData['raw_xmin']), 

294 int(ampData['raw_ymin'])), 

295 lsst.geom.Point2I(int(ampData['raw_xmax']), 

296 int(ampData['raw_ymax']))) 

297 rawDataBbox = lsst.geom.Box2I( 

298 lsst.geom.Point2I(int(ampData['raw_data_xmin']), 

299 int(ampData['raw_data_ymin'])), 

300 lsst.geom.Point2I(int(ampData['raw_data_xmax']), 

301 int(ampData['raw_data_ymax']))) 

302 rawHOverscanBbox = lsst.geom.Box2I( 

303 lsst.geom.Point2I(int(ampData['hoscan_xmin']), 

304 int(ampData['hoscan_ymin'])), 

305 lsst.geom.Point2I(int(ampData['hoscan_xmax']), 

306 int(ampData['hoscan_ymax']))) 

307 rawVOverscanBbox = lsst.geom.Box2I( 

308 lsst.geom.Point2I(int(ampData['voscan_xmin']), 

309 int(ampData['voscan_ymin'])), 

310 lsst.geom.Point2I(int(ampData['voscan_xmax']), 

311 int(ampData['voscan_ymax']))) 

312 rawPrescanBbox = lsst.geom.Box2I( 

313 lsst.geom.Point2I(int(ampData['pscan_xmin']), 

314 int(ampData['pscan_ymin'])), 

315 lsst.geom.Point2I(int(ampData['pscan_xmax']), 

316 int(ampData['pscan_ymax']))) 

317 xoffset = int(ampData['x_offset']) 

318 yoffset = int(ampData['y_offset']) 

319 flipx = bool(int(ampData['flipx'])) 

320 flipy = bool(int(ampData['flipy'])) 

321 readcorner = 'LL' 

322 if not isLsstLike: 

323 offext = lsst.geom.Extent2I(xoffset, yoffset) 

324 if flipx: 

325 xExt = rawBbox.getDimensions().getX() 

326 rawBbox.flipLR(xExt) 

327 rawDataBbox.flipLR(xExt) 

328 rawHOverscanBbox.flipLR(xExt) 

329 rawVOverscanBbox.flipLR(xExt) 

330 rawPrescanBbox.flipLR(xExt) 

331 if flipy: 

332 yExt = rawBbox.getDimensions().getY() 

333 rawBbox.flipTB(yExt) 

334 rawDataBbox.flipTB(yExt) 

335 rawHOverscanBbox.flipTB(yExt) 

336 rawVOverscanBbox.flipTB(yExt) 

337 rawPrescanBbox.flipTB(yExt) 

338 if not flipx and not flipy: 

339 readcorner = 'LL' 

340 elif flipx and not flipy: 

341 readcorner = 'LR' 

342 elif flipx and flipy: 

343 readcorner = 'UR' 

344 elif not flipx and flipy: 

345 readcorner = 'UL' 

346 else: 

347 raise RuntimeError("Couldn't find read corner") 

348 

349 flipx = False 

350 flipy = False 

351 rawBbox.shift(offext) 

352 rawDataBbox.shift(offext) 

353 rawHOverscanBbox.shift(offext) 

354 rawVOverscanBbox.shift(offext) 

355 rawPrescanBbox.shift(offext) 

356 xoffset = 0 

357 yoffset = 0 

358 offset = lsst.geom.Extent2I(xoffset, yoffset) 

359 builder.setBBox(bbox) 

360 builder.setRawXYOffset(offset) 

361 builder.setName(str(ampData['name'])) 

362 builder.setReadoutCorner(readoutMap[readcorner]) 

363 builder.setGain(float(ampData['gain'])) 

364 builder.setReadNoise(float(ampData['readnoise'])) 

365 linCoeffs = np.array([float(ampData['lin_coeffs']), ], dtype=float) 

366 builder.setLinearityCoeffs(linCoeffs) 

367 builder.setLinearityType(str(ampData['lin_type'])) 

368 builder.setRawFlipX(flipx) 

369 builder.setRawFlipY(flipy) 

370 builder.setRawBBox(rawBbox) 

371 builder.setRawDataBBox(rawDataBbox) 

372 builder.setRawHorizontalOverscanBBox(rawHOverscanBbox) 

373 builder.setRawVerticalOverscanBBox(rawVOverscanBbox) 

374 builder.setRawPrescanBBox(rawPrescanBbox) 

375 builder.setLinearityThreshold(float(ampData['lin_thresh'])) 

376 builder.setLinearityMaximum(float(ampData['lin_max'])) 

377 builder.setLinearityUnits(str(ampData['lin_units'])) 

378 self.ampDataDict[ampData['ccd_name']]['linInfo'][ampData['name']] = \ 

379 {'lincoeffs': linCoeffs, 'lintype': str(ampData['lin_type']), 

380 'linthresh': float(ampData['lin_thresh']), 'linmax': float(ampData['lin_max']), 

381 'linunits': str(ampData['lin_units'])} 

382 ampList.append(builder) 

383 return ampListDict 

384 

385 def makeTestRepositoryItems(self, isLsstLike=False): 

386 """Make camera config and amp catalog dictionary, using default 

387 detector and amp files. 

388 

389 Parameters 

390 ---------- 

391 isLsstLike : `bool` 

392 If True then there is one raw image per amplifier; 

393 if False then there is one raw image per detector. 

394 """ 

395 detFile = os.path.join(self._afwTestDataDir, "testCameraDetectors.dat") 

396 detectorConfigs = self.makeDetectorConfigs(detFile) 

397 ampFile = os.path.join(self._afwTestDataDir, "testCameraAmps.dat") 

398 ampListDict = self.makeAmpLists(ampFile, isLsstLike=isLsstLike) 

399 camConfig = CameraConfig() 

400 camConfig.name = "testCamera%s"%('LSST' if isLsstLike else 'SC') 

401 camConfig.detectorList = dict((i, detConfig) 

402 for i, detConfig in enumerate(detectorConfigs)) 

403 camConfig.plateScale = self.plateScale 

404 pScaleRad = lsst.geom.arcsecToRad(self.plateScale) 

405 radialDistortCoeffs = [0.0, 1.0/pScaleRad, 

406 0.0, self.radialDistortion/pScaleRad] 

407 tConfig = afwGeom.TransformConfig() 

408 tConfig.transform.name = 'inverted' 

409 radialClass = afwGeom.transformRegistry['radial'] 

410 tConfig.transform.active.transform.retarget(radialClass) 

411 tConfig.transform.active.transform.coeffs = radialDistortCoeffs 

412 tmc = TransformMapConfig() 

413 tmc.nativeSys = FOCAL_PLANE.getSysName() 

414 tmc.transforms = {FIELD_ANGLE.getSysName(): tConfig} 

415 camConfig.transformDict = tmc 

416 return camConfig, ampListDict 

417 

418 

419@inTestCase 

420def compare2DFunctions(self, func1, func2, minVal=-10, maxVal=None, nVal=5): 

421 """Compare two Point2D(Point2D) functions by evaluating them over a 

422 range of values. 

423 """ 

424 if maxVal is None: 

425 maxVal = -minVal 

426 dVal = (maxVal - minVal) / (nVal - 1) 

427 for xInd in range(nVal): 

428 x = minVal + (xInd * dVal) 

429 for yInd in range(nVal): 

430 y = minVal + (yInd * dVal) 

431 fromPoint = lsst.geom.Point2D(x, y) 

432 res1 = func1(fromPoint) 

433 res2 = func2(fromPoint) 

434 self.assertPairsAlmostEqual(res1, res2) 

435 

436 

437@inTestCase 

438def assertTransformMapsEqual(self, map1, map2, **kwds): 

439 """Compare two TransformMaps. 

440 """ 

441 self.assertEqual(list(map1), list(map2)) # compares the sets of CameraSys 

442 for sysFrom in map1: 

443 for sysTo in map1: 

444 with self.subTest(sysFrom=sysFrom, sysTo=sysTo): 

445 transform1 = map1.getTransform(sysFrom, sysTo) 

446 transform2 = map2.getTransform(sysFrom, sysTo) 

447 self.compare2DFunctions(transform1.applyForward, transform2.applyForward, **kwds) 

448 self.compare2DFunctions(transform1.applyInverse, transform2.applyInverse, **kwds) 

449 

450 

451@inTestCase 

452def assertAmplifiersEqual(self, amp1, amp2): 

453 self.assertEqual(amp1.getName(), amp2.getName()) 

454 self.assertEqual(amp1.getBBox(), amp2.getBBox()) 

455 self.assertFloatsEqual(amp1.getGain(), amp2.getGain(), ignoreNaNs=True) 

456 self.assertFloatsEqual(amp1.getReadNoise(), amp2.getReadNoise(), ignoreNaNs=True) 

457 self.assertFloatsEqual(amp1.getSaturation(), amp2.getSaturation(), ignoreNaNs=True) 

458 self.assertEqual(amp1.getReadoutCorner(), amp2.getReadoutCorner()) 

459 self.assertFloatsEqual(amp1.getSuspectLevel(), amp2.getSuspectLevel(), ignoreNaNs=True) 

460 self.assertEqual(amp1.getLinearityCoeffs().shape, amp2.getLinearityCoeffs().shape) 

461 self.assertFloatsEqual(amp1.getLinearityCoeffs(), amp2.getLinearityCoeffs(), ignoreNaNs=True) 

462 self.assertEqual(amp1.getLinearityType(), amp2.getLinearityType()) 

463 self.assertFloatsEqual(amp1.getLinearityThreshold(), amp2.getLinearityThreshold(), ignoreNaNs=True) 

464 self.assertFloatsEqual(amp1.getLinearityMaximum(), amp2.getLinearityMaximum(), ignoreNaNs=True) 

465 self.assertEqual(amp1.getLinearityUnits(), amp2.getLinearityUnits()) 

466 self.assertEqual(amp1.getRawBBox(), amp2.getRawBBox()) 

467 self.assertEqual(amp1.getRawDataBBox(), amp2.getRawDataBBox()) 

468 self.assertEqual(amp1.getRawFlipX(), amp2.getRawFlipX()) 

469 self.assertEqual(amp1.getRawFlipY(), amp2.getRawFlipY()) 

470 self.assertEqual(amp1.getRawHorizontalOverscanBBox(), amp2.getRawHorizontalOverscanBBox()) 

471 self.assertEqual(amp1.getRawVerticalOverscanBBox(), amp2.getRawVerticalOverscanBBox()) 

472 self.assertEqual(amp1.getRawPrescanBBox(), amp2.getRawPrescanBBox()) 

473 

474 

475@inTestCase 

476def assertDetectorsEqual(self, detector1, detector2, *, compareTransforms=True, **kwds): 

477 """Compare two Detectors. 

478 """ 

479 self.assertEqual(detector1.getName(), detector2.getName()) 

480 self.assertEqual(detector1.getId(), detector2.getId()) 

481 self.assertEqual(detector1.getSerial(), detector2.getSerial()) 

482 self.assertEqual(detector1.getPhysicalType(), detector2.getPhysicalType()) 

483 self.assertEqual(detector1.getBBox(), detector2.getBBox()) 

484 self.assertEqual(detector1.getPixelSize(), detector2.getPixelSize()) 

485 orientationIn = detector1.getOrientation() 

486 orientationOut = detector2.getOrientation() 

487 self.assertEqual(orientationIn.getFpPosition(), orientationOut.getFpPosition()) 

488 self.assertEqual(orientationIn.getReferencePoint(), orientationOut.getReferencePoint()) 

489 self.assertEqual(orientationIn.getYaw(), orientationOut.getYaw()) 

490 self.assertEqual(orientationIn.getPitch(), orientationOut.getPitch()) 

491 self.assertEqual(orientationIn.getRoll(), orientationOut.getRoll()) 

492 self.assertFloatsEqual(detector1.getCrosstalk(), detector2.getCrosstalk()) 

493 if compareTransforms: 

494 self.assertTransformMapsEqual(detector1.getTransformMap(), detector2.getTransformMap(), **kwds) 

495 self.assertEqual(len(detector1.getAmplifiers()), len(detector2.getAmplifiers())) 

496 for amp1, amp2 in zip(detector1.getAmplifiers(), detector2.getAmplifiers()): 

497 self.assertAmplifiersEqual(amp1, amp2) 

498 

499 

500@inTestCase 

501def assertDetectorCollectionsEqual(self, collection1, collection2, **kwds): 

502 """Compare two DetectorCollections. 

503 """ 

504 self.assertCountEqual(list(collection1.getNameIter()), list(collection2.getNameIter())) 

505 for k in collection1.getNameIter(): 

506 self.assertDetectorsEqual(collection1[k], collection2[k], **kwds) 

507 

508 

509@inTestCase 

510def assertCamerasEqual(self, camera1, camera2, **kwds): 

511 """Compare two Camers. 

512 """ 

513 self.assertDetectorCollectionsEqual(camera1, camera2, **kwds) 

514 self.assertTransformMapsEqual(camera1.getTransformMap(), camera2.getTransformMap()) 

515 self.assertEqual(camera1.getName(), camera2.getName()) 

516 self.assertEqual(camera1.getPupilFactoryName(), camera2.getPupilFactoryName())