22__all__ = [
"DetectorWrapper",
"CameraWrapper"]
24import importlib.resources
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):
205 isLsstLike, focalPlaneParity=focalPlaneParity)
211 """Return the number of detectors"""
215 """Construct a list of DetectorConfig, one per detector
220 with open(detFile)
as fh:
221 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
223 els = line.rstrip().split(
"|")
224 detectorProps = dict([(name, el)
225 for name, el
in zip(names, els)])
226 detectors.append(detectorProps)
228 for i, detector
in enumerate(detectors):
229 detectorId = (i + 1) * 10
230 detectorName = detector[
'name']
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)
255 return detectorConfigs
258 """Construct a dict of list of Amplifer, one list per detector.
263 Path to amplifier data file.
265 If True then there is one raw image per amplifier;
266 if False then there is one raw image per detector.
269 'LL': ReadoutCorner.LL,
270 'LR': ReadoutCorner.LR,
271 'UR': ReadoutCorner.UR,
272 'UL': ReadoutCorner.UL,
275 with open(ampFile)
as fh:
276 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
278 els = line.rstrip().split(
"|")
279 ampProps = dict([(name, el)
for name, el
in zip(names, els)])
280 ampDataList.append(ampProps)
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
289 ampListDict[ampData[
'ccd_name']] = ampList
290 self.
ampDataDict[ampData[
'ccd_name']] = {
'namps': 1,
'linInfo': {}}
293 int(ampData[
'trimmed_ymin'])),
295 int(ampData[
'trimmed_ymax'])))
297 int(ampData[
'raw_ymin'])),
299 int(ampData[
'raw_ymax'])))
302 int(ampData[
'raw_data_ymin'])),
304 int(ampData[
'raw_data_ymax'])))
307 int(ampData[
'hoscan_ymin'])),
309 int(ampData[
'hoscan_ymax'])))
312 int(ampData[
'voscan_ymin'])),
314 int(ampData[
'voscan_ymax'])))
317 int(ampData[
'pscan_ymin'])),
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']))
328 xExt = rawBbox.getDimensions().getX()
330 rawDataBbox.flipLR(xExt)
331 rawHOverscanBbox.flipLR(xExt)
332 rawVOverscanBbox.flipLR(xExt)
333 rawPrescanBbox.flipLR(xExt)
335 yExt = rawBbox.getDimensions().getY()
337 rawDataBbox.flipTB(yExt)
338 rawHOverscanBbox.flipTB(yExt)
339 rawVOverscanBbox.flipTB(yExt)
340 rawPrescanBbox.flipTB(yExt)
341 if not flipx
and not flipy:
343 elif flipx
and not flipy:
345 elif flipx
and flipy:
347 elif not flipx
and flipy:
350 raise RuntimeError(
"Couldn't find read corner")
354 rawBbox.shift(offext)
355 rawDataBbox.shift(offext)
356 rawHOverscanBbox.shift(offext)
357 rawVOverscanBbox.shift(offext)
358 rawPrescanBbox.shift(offext)
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)
389 """Make camera config and amp catalog dictionary, using default
390 detector and amp files.
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.
401 detPath = os.path.join(self.
_afwTestDataDir,
"testCameraDetectors.dat")
402 with importlib.resources.path(
"lsst.afw", detPath)
as detFile:
405 with importlib.resources.path(
"lsst.afw", ampPath)
as ampFile:
406 ampListDict = self.
makeAmpLists(ampFile, isLsstLike=isLsstLike)
408 camConfig.name =
"testCamera%s"%(
'LSST' if isLsstLike
else 'SC')
409 camConfig.detectorList = dict((i, detConfig)
410 for i, detConfig
in enumerate(detectorConfigs))
412 camConfig.focalPlaneParity = focalPlaneParity
414 radialDistortCoeffs = [0.0, 1.0/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
422 tmc.nativeSys = FOCAL_PLANE.getSysName()
423 tmc.transforms = {FIELD_ANGLE.getSysName(): tConfig}
424 camConfig.transformDict = tmc
425 return camConfig, ampListDict
430 """Compare two Point2D(list(Point2D)) functions by evaluating them over a
435 Assumes the functions can be called with ``list[Point2D]`` and return
440 dVal = (maxVal - minVal) / (nVal - 1)
442 for xInd
in range(nVal):
443 x = minVal + (xInd * dVal)
444 for yInd
in range(nVal):
445 y = minVal + (yInd * dVal)
447 points.append(fromPoint)
449 vres1 = func1(points)
450 vres2 = func2(points)
451 for res1, res2
in zip(vres1, vres2):
452 self.assertPairsAlmostEqual(res1, res2)
457 """Compare two TransformMaps.
459 self.assertEqual(list(map1), list(map2))
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)
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())
495 """Compare two Detectors.
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)
520 """Compare two DetectorCollections.
522 self.assertCountEqual(list(collection1.getNameIter()), list(collection2.getNameIter()))
523 for k
in collection1.getNameIter():
524 self.assertDetectorsEqual(collection1[k], collection2[k], **kwds)
529 """Compare two Cameras.
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())
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