Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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.assertEqual(amp1.getGain(), amp2.getGain()) 

456 self.assertEqual(amp1.getReadNoise(), amp2.getReadNoise()) 

457 self.assertEqual(amp1.getSaturation(), amp2.getSaturation()) 

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

459 self.assertEqual(amp1.getSuspectLevel(), amp2.getSuspectLevel()) 

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

461 self.assertFloatsEqual(amp1.getLinearityCoeffs(), amp2.getLinearityCoeffs()) 

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

463 self.assertEqual(amp1.getLinearityThreshold(), amp2.getLinearityThreshold()) 

464 self.assertEqual(amp1.getLinearityMaximum(), amp2.getLinearityMaximum()) 

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, **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 self.assertTransformMapsEqual(detector1.getTransformMap(), detector2.getTransformMap(), **kwds) 

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

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

496 self.assertAmplifiersEqual(amp1, amp2) 

497 

498 

499@inTestCase 

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

501 """Compare two DetectorCollections. 

502 """ 

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

504 for k in collection1.getNameIter(): 

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

506 

507 

508@inTestCase 

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

510 """Compare two Camers. 

511 """ 

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

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

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

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