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

312 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 01:21 -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 importlib.resources 

25import os 

26 

27import numpy as np 

28 

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 focalPlaneParity : `bool` 

188 If `True`, the X axis is flipped between the FOCAL_PLANE and 

189 FIELD_ANGLE coordinate systems. 

190 """ 

191 

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

193 # Path to test data within lsst.afw package. 

194 self._afwTestDataDir = "cameraGeom/testData/" 

195 

196 # Info to store for unit tests 

197 self.plateScale = float(plateScale) 

198 self.radialDistortion = float(radialDistortion) 

199 self.detectorNameList = [] 

200 self.detectorIdList = [] 

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

202 

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

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

205 isLsstLike, focalPlaneParity=focalPlaneParity) 

206 self.camera = makeCameraFromAmpLists( 

207 self.camConfig, self.ampListDict) 

208 

209 @property 

210 def nDetectors(self): 

211 """Return the number of detectors""" 

212 return len(self.detectorNameList) 

213 

214 def makeDetectorConfigs(self, detFile): 

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

216 """ 

217 detectors = [] 

218 self.detectorNameList = [] 

219 self.detectorIdList = [] 

220 with open(detFile) as fh: 

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

222 for line in fh: 

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

224 detectorProps = dict([(name, el) 

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

226 detectors.append(detectorProps) 

227 detectorConfigs = [] 

228 for i, detector in enumerate(detectors): 

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

230 detectorName = detector['name'] 

231 detConfig = DetectorConfig() 

232 detConfig.name = detectorName 

233 detConfig.id = detectorId 

234 detConfig.bbox_x0 = 0 

235 detConfig.bbox_y0 = 0 

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

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

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

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

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

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

242 detConfig.offset_z = float(detector['z']) 

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

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

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

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

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

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

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

250 detConfig.transposeDetector = False 

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

252 detectorConfigs.append(detConfig) 

253 self.detectorNameList.append(detectorName) 

254 self.detectorIdList.append(detectorId) 

255 return detectorConfigs 

256 

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

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

259 

260 Parameters 

261 ---------- 

262 ampFile : `str` 

263 Path to amplifier data file. 

264 isLsstLike : `bool` 

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

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

267 """ 

268 readoutMap = { 

269 'LL': ReadoutCorner.LL, 

270 'LR': ReadoutCorner.LR, 

271 'UR': ReadoutCorner.UR, 

272 'UL': ReadoutCorner.UL, 

273 } 

274 ampDataList = [] 

275 with open(ampFile) as fh: 

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

277 for line in fh: 

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

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

280 ampDataList.append(ampProps) 

281 ampListDict = {} 

282 self.ampDataDict = {} 

283 for ampData in ampDataList: 

284 if ampData['ccd_name'] in ampListDict: 

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

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

287 else: 

288 ampList = [] 

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

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

291 builder = Amplifier.Builder() 

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

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

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

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

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

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

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

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

300 rawDataBbox = lsst.geom.Box2I( 

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

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

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

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

305 rawHOverscanBbox = lsst.geom.Box2I( 

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

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

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

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

310 rawVOverscanBbox = lsst.geom.Box2I( 

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

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

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

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

315 rawPrescanBbox = lsst.geom.Box2I( 

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

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

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

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

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

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

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

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

324 readcorner = 'LL' 

325 if not isLsstLike: 

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

327 if flipx: 

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

329 rawBbox.flipLR(xExt) 

330 rawDataBbox.flipLR(xExt) 

331 rawHOverscanBbox.flipLR(xExt) 

332 rawVOverscanBbox.flipLR(xExt) 

333 rawPrescanBbox.flipLR(xExt) 

334 if flipy: 

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

336 rawBbox.flipTB(yExt) 

337 rawDataBbox.flipTB(yExt) 

338 rawHOverscanBbox.flipTB(yExt) 

339 rawVOverscanBbox.flipTB(yExt) 

340 rawPrescanBbox.flipTB(yExt) 

341 if not flipx and not flipy: 

342 readcorner = 'LL' 

343 elif flipx and not flipy: 

344 readcorner = 'LR' 

345 elif flipx and flipy: 

346 readcorner = 'UR' 

347 elif not flipx and flipy: 

348 readcorner = 'UL' 

349 else: 

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

351 

352 flipx = False 

353 flipy = False 

354 rawBbox.shift(offext) 

355 rawDataBbox.shift(offext) 

356 rawHOverscanBbox.shift(offext) 

357 rawVOverscanBbox.shift(offext) 

358 rawPrescanBbox.shift(offext) 

359 xoffset = 0 

360 yoffset = 0 

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

362 builder.setBBox(bbox) 

363 builder.setRawXYOffset(offset) 

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

365 builder.setReadoutCorner(readoutMap[readcorner]) 

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

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

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

369 builder.setLinearityCoeffs(linCoeffs) 

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

371 builder.setRawFlipX(flipx) 

372 builder.setRawFlipY(flipy) 

373 builder.setRawBBox(rawBbox) 

374 builder.setRawDataBBox(rawDataBbox) 

375 builder.setRawHorizontalOverscanBBox(rawHOverscanBbox) 

376 builder.setRawVerticalOverscanBBox(rawVOverscanBbox) 

377 builder.setRawPrescanBBox(rawPrescanBbox) 

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

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

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

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

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

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

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

385 ampList.append(builder) 

386 return ampListDict 

387 

388 def makeTestRepositoryItems(self, isLsstLike=False, focalPlaneParity=False): 

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

390 detector and amp files. 

391 

392 Parameters 

393 ---------- 

394 isLsstLike : `bool` 

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

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

397 focalPlaneParity : `bool` 

398 If `True`, the X axis is flipped between the FOCAL_PLANE and 

399 FIELD_ANGLE coordinate systems. 

400 """ 

401 detPath = os.path.join(self._afwTestDataDir, "testCameraDetectors.dat") 

402 with importlib.resources.path("lsst.afw", detPath) as detFile: 

403 detectorConfigs = self.makeDetectorConfigs(detFile) 

404 ampPath = os.path.join(self._afwTestDataDir, "testCameraAmps.dat") 

405 with importlib.resources.path("lsst.afw", ampPath) as ampFile: 

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

407 camConfig = CameraConfig() 

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

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

410 for i, detConfig in enumerate(detectorConfigs)) 

411 camConfig.plateScale = self.plateScale 

412 camConfig.focalPlaneParity = focalPlaneParity 

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

414 radialDistortCoeffs = [0.0, 1.0/pScaleRad, 

415 0.0, self.radialDistortion/pScaleRad] 

416 tConfig = afwGeom.TransformConfig() 

417 tConfig.transform.name = 'inverted' 

418 radialClass = afwGeom.transformRegistry['radial'] 

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

420 tConfig.transform.active.transform.coeffs = radialDistortCoeffs 

421 tmc = TransformMapConfig() 

422 tmc.nativeSys = FOCAL_PLANE.getSysName() 

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

424 camConfig.transformDict = tmc 

425 return camConfig, ampListDict 

426 

427 

428@inTestCase 

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

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

431 range of values. 

432 

433 Notes 

434 ----- 

435 Assumes the functions can be called with ``list[Point2D]`` and return 

436 ``list[Point2D]``. 

437 """ 

438 if maxVal is None: 

439 maxVal = -minVal 

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

441 points = [] 

442 for xInd in range(nVal): 

443 x = minVal + (xInd * dVal) 

444 for yInd in range(nVal): 

445 y = minVal + (yInd * dVal) 

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

447 points.append(fromPoint) 

448 

449 vres1 = func1(points) 

450 vres2 = func2(points) 

451 for res1, res2 in zip(vres1, vres2): 

452 self.assertPairsAlmostEqual(res1, res2) 

453 

454 

455@inTestCase 

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

457 """Compare two TransformMaps. 

458 """ 

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

460 for sysFrom in map1: 

461 for sysTo in map1: 

462 with self.subTest(sysFrom=repr(sysFrom), sysTo=repr(sysTo)): 

463 transform1 = map1.getTransform(sysFrom, sysTo) 

464 transform2 = map2.getTransform(sysFrom, sysTo) 

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

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

467 

468 

469@inTestCase 

470def assertAmplifiersEqual(self, amp1, amp2): 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

491 

492 

493@inTestCase 

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

495 """Compare two Detectors. 

496 """ 

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

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

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

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

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

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

503 orientationIn = detector1.getOrientation() 

504 orientationOut = detector2.getOrientation() 

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

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

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

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

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

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

511 if compareTransforms: 

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

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

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

515 self.assertAmplifiersEqual(amp1, amp2) 

516 

517 

518@inTestCase 

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

520 """Compare two DetectorCollections. 

521 """ 

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

523 for k in collection1.getNameIter(): 

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

525 

526 

527@inTestCase 

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

529 """Compare two Cameras. 

530 """ 

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

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

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

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

535 self.assertEqual(camera1.getFocalPlaneParity(), camera2.getFocalPlaneParity())