22__all__ = [
"DetectorWrapper",
"CameraWrapper"]
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
43 """A Detector and the data used to construct it
45 Intended for use with unit tests, thus saves a copy of all input parameters.
46 Does not support setting details of amplifiers.
50 name : `str` (optional)
54 detType : `lsst.afw.cameraGeom.DetectorType` (optional)
56 serial : `str` (optional)
58 bbox : `lsst.geom.Box2I` (optional)
59 Bounding box; defaults to (0, 0), (1024x1024).
60 numAmps : `int` (optional)
62 pixelSize : `lsst.geom.Point2D` (optional)
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
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
89 detType=DetectorType.SCIENCE,
93 pixelSize=(0.02, 0.02),
97 radialDistortion=0.925,
120 radialDistortCoeffs = [0.0, 1.0/pScaleRad,
122 focalPlaneToField = afwGeom.makeRadialTransform(radialDistortCoeffs)
126 focalPlaneToField=focalPlaneToField,
133 tanPixelSys: pixelToTanPixel,
134 actualPixelSys: afwGeom.makeRadialTransform([0, 0.95, 0.01]),
136 if crosstalk
is None:
137 crosstalk = [[0.0
for _
in range(numAmps)]
for _
in range(numAmps)]
140 if cameraBuilder
is None:
143 for i
in range(numAmps):
145 ampName = f
"amp {i + 1}"
146 ampBuilder.setName(ampName)
148 ampBuilder.setGain(1.71234e3)
149 ampBuilder.setReadNoise(0.521237e2)
150 ampBuilder.setReadoutCorner(ReadoutCorner.LL)
151 self.
ampList.append(ampBuilder)
154 detectorBuilder = cameraBuilder.add(self.
name, self.
id)
155 detectorBuilder.setType(self.
type)
156 detectorBuilder.setSerial(self.
serial)
158 detectorBuilder.setBBox(self.
bbox)
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()
171 """A simple Camera and the data used to construct it
173 Intended for use with unit tests, thus saves some interesting information.
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.
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.
192 def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False, focalPlaneParity=False):
193 afwDir = lsst.utils.getPackageDir(
"afw")
195 "cameraGeom",
"testData")
206 isLsstLike, focalPlaneParity=focalPlaneParity)
212 """Return the number of detectors"""
216 """Construct a list of DetectorConfig, one per detector
221 with open(detFile)
as fh:
222 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
224 els = line.rstrip().split(
"|")
225 detectorProps = dict([(name, el)
226 for name, el
in zip(names, els)])
227 detectors.append(detectorProps)
229 for i, detector
in enumerate(detectors):
230 detectorId = (i + 1) * 10
231 detectorName = detector[
'name']
233 detConfig.name = detectorName
234 detConfig.id = detectorId
235 detConfig.bbox_x0 = 0
236 detConfig.bbox_y0 = 0
237 detConfig.bbox_x1 = int(detector[
'npix_x']) - 1
238 detConfig.bbox_y1 = int(detector[
'npix_y']) - 1
239 detConfig.serial = str(detector[
'serial'])
240 detConfig.detectorType = int(detector[
'detectorType'])
241 detConfig.offset_x = float(detector[
'x'])
242 detConfig.offset_y = float(detector[
'y'])
243 detConfig.offset_z = float(detector[
'z'])
244 detConfig.refpos_x = float(detector[
'refPixPos_x'])
245 detConfig.refpos_y = float(detector[
'refPixPos_y'])
246 detConfig.yawDeg = float(detector[
'yaw'])
247 detConfig.pitchDeg = float(detector[
'pitch'])
248 detConfig.rollDeg = float(detector[
'roll'])
249 detConfig.pixelSize_x = float(detector[
'pixelSize'])
250 detConfig.pixelSize_y = float(detector[
'pixelSize'])
251 detConfig.transposeDetector =
False
252 detConfig.transformDict.nativeSys = PIXELS.getSysName()
253 detectorConfigs.append(detConfig)
256 return detectorConfigs
259 """Construct a dict of list of Amplifer, one list per detector.
264 Path to amplifier data file.
266 If True then there is one raw image per amplifier;
267 if False then there is one raw image per detector.
270 'LL': ReadoutCorner.LL,
271 'LR': ReadoutCorner.LR,
272 'UR': ReadoutCorner.UR,
273 'UL': ReadoutCorner.UL,
276 with open(ampFile)
as fh:
277 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
279 els = line.rstrip().split(
"|")
280 ampProps = dict([(name, el)
for name, el
in zip(names, els)])
281 ampDataList.append(ampProps)
284 for ampData
in ampDataList:
285 if ampData[
'ccd_name']
in ampListDict:
286 ampList = ampListDict[ampData[
'ccd_name']]
287 self.
ampDataDict[ampData[
'ccd_name']][
'namps'] += 1
290 ampListDict[ampData[
'ccd_name']] = ampList
291 self.
ampDataDict[ampData[
'ccd_name']] = {
'namps': 1,
'linInfo': {}}
294 int(ampData[
'trimmed_ymin'])),
296 int(ampData[
'trimmed_ymax'])))
298 int(ampData[
'raw_ymin'])),
300 int(ampData[
'raw_ymax'])))
303 int(ampData[
'raw_data_ymin'])),
305 int(ampData[
'raw_data_ymax'])))
308 int(ampData[
'hoscan_ymin'])),
310 int(ampData[
'hoscan_ymax'])))
313 int(ampData[
'voscan_ymin'])),
315 int(ampData[
'voscan_ymax'])))
318 int(ampData[
'pscan_ymin'])),
320 int(ampData[
'pscan_ymax'])))
321 xoffset = int(ampData[
'x_offset'])
322 yoffset = int(ampData[
'y_offset'])
323 flipx = bool(int(ampData[
'flipx']))
324 flipy = bool(int(ampData[
'flipy']))
329 xExt = rawBbox.getDimensions().getX()
331 rawDataBbox.flipLR(xExt)
332 rawHOverscanBbox.flipLR(xExt)
333 rawVOverscanBbox.flipLR(xExt)
334 rawPrescanBbox.flipLR(xExt)
336 yExt = rawBbox.getDimensions().getY()
338 rawDataBbox.flipTB(yExt)
339 rawHOverscanBbox.flipTB(yExt)
340 rawVOverscanBbox.flipTB(yExt)
341 rawPrescanBbox.flipTB(yExt)
342 if not flipx
and not flipy:
344 elif flipx
and not flipy:
346 elif flipx
and flipy:
348 elif not flipx
and flipy:
351 raise RuntimeError(
"Couldn't find read corner")
355 rawBbox.shift(offext)
356 rawDataBbox.shift(offext)
357 rawHOverscanBbox.shift(offext)
358 rawVOverscanBbox.shift(offext)
359 rawPrescanBbox.shift(offext)
363 builder.setBBox(bbox)
364 builder.setRawXYOffset(offset)
365 builder.setName(str(ampData[
'name']))
366 builder.setReadoutCorner(readoutMap[readcorner])
367 builder.setGain(float(ampData[
'gain']))
368 builder.setReadNoise(float(ampData[
'readnoise']))
369 linCoeffs = np.array([float(ampData[
'lin_coeffs']), ], dtype=float)
370 builder.setLinearityCoeffs(linCoeffs)
371 builder.setLinearityType(str(ampData[
'lin_type']))
372 builder.setRawFlipX(flipx)
373 builder.setRawFlipY(flipy)
374 builder.setRawBBox(rawBbox)
375 builder.setRawDataBBox(rawDataBbox)
376 builder.setRawHorizontalOverscanBBox(rawHOverscanBbox)
377 builder.setRawVerticalOverscanBBox(rawVOverscanBbox)
378 builder.setRawPrescanBBox(rawPrescanBbox)
379 builder.setLinearityThreshold(float(ampData[
'lin_thresh']))
380 builder.setLinearityMaximum(float(ampData[
'lin_max']))
381 builder.setLinearityUnits(str(ampData[
'lin_units']))
382 self.
ampDataDict[ampData[
'ccd_name']][
'linInfo'][ampData[
'name']] = \
383 {
'lincoeffs': linCoeffs,
'lintype': str(ampData[
'lin_type']),
384 'linthresh': float(ampData[
'lin_thresh']),
'linmax': float(ampData[
'lin_max']),
385 'linunits': str(ampData[
'lin_units'])}
386 ampList.append(builder)
390 """Make camera config and amp catalog dictionary, using default
391 detector and amp files.
396 If True then there is one raw image per amplifier;
397 if False then there is one raw image per detector.
398 focalPlaneParity : `bool`
399 If `True`, the X axis is flipped between the FOCAL_PLANE and
400 FIELD_ANGLE coordinate systems.
402 detFile = os.path.join(self.
_afwTestDataDir,
"testCameraDetectors.dat")
405 ampListDict = self.
makeAmpLists(ampFile, isLsstLike=isLsstLike)
407 camConfig.name =
"testCamera%s"%(
'LSST' if isLsstLike
else 'SC')
408 camConfig.detectorList = dict((i, detConfig)
409 for i, detConfig
in enumerate(detectorConfigs))
411 camConfig.focalPlaneParity = focalPlaneParity
413 radialDistortCoeffs = [0.0, 1.0/pScaleRad,
415 tConfig = afwGeom.TransformConfig()
416 tConfig.transform.name =
'inverted'
417 radialClass = afwGeom.transformRegistry[
'radial']
418 tConfig.transform.active.transform.retarget(radialClass)
419 tConfig.transform.active.transform.coeffs = radialDistortCoeffs
421 tmc.nativeSys = FOCAL_PLANE.getSysName()
422 tmc.transforms = {FIELD_ANGLE.getSysName(): tConfig}
423 camConfig.transformDict = tmc
424 return camConfig, ampListDict
429 """Compare two Point2D(list(Point2D)) functions by evaluating them over a
434 Assumes the functions can be called with ``list[Point2D]`` and return
439 dVal = (maxVal - minVal) / (nVal - 1)
441 for xInd
in range(nVal):
442 x = minVal + (xInd * dVal)
443 for yInd
in range(nVal):
444 y = minVal + (yInd * dVal)
446 points.append(fromPoint)
448 vres1 = func1(points)
449 vres2 = func2(points)
450 for res1, res2
in zip(vres1, vres2):
451 self.assertPairsAlmostEqual(res1, res2)
456 """Compare two TransformMaps.
458 self.assertEqual(list(map1), list(map2))
461 with self.subTest(sysFrom=repr(sysFrom), sysTo=repr(sysTo)):
462 transform1 = map1.getTransform(sysFrom, sysTo)
463 transform2 = map2.getTransform(sysFrom, sysTo)
464 self.compare2DFunctions(transform1.applyForward, transform2.applyForward, **kwds)
465 self.compare2DFunctions(transform1.applyInverse, transform2.applyInverse, **kwds)
470 self.assertEqual(amp1.getName(), amp2.getName())
471 self.assertEqual(amp1.getBBox(), amp2.getBBox())
472 self.assertFloatsEqual(amp1.getGain(), amp2.getGain(), ignoreNaNs=
True)
473 self.assertFloatsEqual(amp1.getReadNoise(), amp2.getReadNoise(), ignoreNaNs=
True)
474 self.assertFloatsEqual(amp1.getSaturation(), amp2.getSaturation(), ignoreNaNs=
True)
475 self.assertEqual(amp1.getReadoutCorner(), amp2.getReadoutCorner())
476 self.assertFloatsEqual(amp1.getSuspectLevel(), amp2.getSuspectLevel(), ignoreNaNs=
True)
477 self.assertEqual(amp1.getLinearityCoeffs().shape, amp2.getLinearityCoeffs().shape)
478 self.assertFloatsEqual(amp1.getLinearityCoeffs(), amp2.getLinearityCoeffs(), ignoreNaNs=
True)
479 self.assertEqual(amp1.getLinearityType(), amp2.getLinearityType())
480 self.assertFloatsEqual(amp1.getLinearityThreshold(), amp2.getLinearityThreshold(), ignoreNaNs=
True)
481 self.assertFloatsEqual(amp1.getLinearityMaximum(), amp2.getLinearityMaximum(), ignoreNaNs=
True)
482 self.assertEqual(amp1.getLinearityUnits(), amp2.getLinearityUnits())
483 self.assertEqual(amp1.getRawBBox(), amp2.getRawBBox())
484 self.assertEqual(amp1.getRawDataBBox(), amp2.getRawDataBBox())
485 self.assertEqual(amp1.getRawFlipX(), amp2.getRawFlipX())
486 self.assertEqual(amp1.getRawFlipY(), amp2.getRawFlipY())
487 self.assertEqual(amp1.getRawHorizontalOverscanBBox(), amp2.getRawHorizontalOverscanBBox())
488 self.assertEqual(amp1.getRawVerticalOverscanBBox(), amp2.getRawVerticalOverscanBBox())
489 self.assertEqual(amp1.getRawPrescanBBox(), amp2.getRawPrescanBBox())
494 """Compare two Detectors.
496 self.assertEqual(detector1.getName(), detector2.getName())
497 self.assertEqual(detector1.getId(), detector2.getId())
498 self.assertEqual(detector1.getSerial(), detector2.getSerial())
499 self.assertEqual(detector1.getPhysicalType(), detector2.getPhysicalType())
500 self.assertEqual(detector1.getBBox(), detector2.getBBox())
501 self.assertEqual(detector1.getPixelSize(), detector2.getPixelSize())
502 orientationIn = detector1.getOrientation()
503 orientationOut = detector2.getOrientation()
504 self.assertEqual(orientationIn.getFpPosition(), orientationOut.getFpPosition())
505 self.assertEqual(orientationIn.getReferencePoint(), orientationOut.getReferencePoint())
506 self.assertEqual(orientationIn.getYaw(), orientationOut.getYaw())
507 self.assertEqual(orientationIn.getPitch(), orientationOut.getPitch())
508 self.assertEqual(orientationIn.getRoll(), orientationOut.getRoll())
509 self.assertFloatsEqual(detector1.getCrosstalk(), detector2.getCrosstalk())
510 if compareTransforms:
511 self.assertTransformMapsEqual(detector1.getTransformMap(), detector2.getTransformMap(), **kwds)
512 self.assertEqual(len(detector1.getAmplifiers()), len(detector2.getAmplifiers()))
513 for amp1, amp2
in zip(detector1.getAmplifiers(), detector2.getAmplifiers()):
514 self.assertAmplifiersEqual(amp1, amp2)
519 """Compare two DetectorCollections.
521 self.assertCountEqual(list(collection1.getNameIter()), list(collection2.getNameIter()))
522 for k
in collection1.getNameIter():
523 self.assertDetectorsEqual(collection1[k], collection2[k], **kwds)
528 """Compare two Cameras.
530 self.assertDetectorCollectionsEqual(camera1, camera2, **kwds)
531 self.assertTransformMapsEqual(camera1.getTransformMap(), camera2.getTransformMap())
532 self.assertEqual(camera1.getName(), camera2.getName())
533 self.assertEqual(camera1.getPupilFactoryName(), camera2.getPupilFactoryName())
534 self.assertEqual(camera1.getFocalPlaneParity(), camera2.getFocalPlaneParity())
A mutable Amplifier subclass class that can be used to incrementally construct or modify Amplifiers.
A helper class for creating and modifying cameras.
Camera coordinate system; used as a key in in TransformMap.
Describe a detector's orientation in the focal plane.
__init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False, focalPlaneParity=False)
makeTestRepositoryItems(self, isLsstLike=False, focalPlaneParity=False)
makeAmpLists(self, ampFile, isLsstLike=False)
makeDetectorConfigs(self, detFile)
__init__(self, name="detector 1", id=1, detType=DetectorType.SCIENCE, serial="xkcd722", bbox=None, numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, crosstalk=None, modFunc=None, physicalType="CCD", cameraBuilder=None)
makeCameraFromAmpLists(cameraConfig, ampListDict, pupilFactoryClass=PupilFactory)
makePixelToTanPixel(bbox, orientation, focalPlaneToField, pixelSizeMm)
assertTransformMapsEqual(self, map1, map2, **kwds)
compare2DFunctions(self, func1, func2, minVal=-10, maxVal=None, nVal=5)
assertDetectorCollectionsEqual(self, collection1, collection2, **kwds)
assertCamerasEqual(self, camera1, camera2, **kwds)
assertDetectorsEqual(self, detector1, detector2, *, compareTransforms=True, **kwds)
assertAmplifiersEqual(self, amp1, amp2)
constexpr double arcsecToRad(double x) noexcept