22 __all__ = [
"DetectorWrapper",
"CameraWrapper"]
32 from .cameraSys
import CameraSys, PIXELS, TAN_PIXELS, FIELD_ANGLE, FOCAL_PLANE, ACTUAL_PIXELS
33 from .orientation
import Orientation
34 from .amplifier
import Amplifier, ReadoutCorner
35 from .camera
import Camera
36 from .detector
import DetectorType
37 from .cameraConfig
import DetectorConfig, CameraConfig
38 from .cameraFactory
import makeCameraFromAmpLists
39 from .makePixelToTanPixel
import makePixelToTanPixel
40 from .transformConfig
import TransformMapConfig
44 """A Detector and the data used to construct it
46 Intended for use with unit tests, thus saves a copy of all input parameters.
47 Does not support setting details of amplifiers.
51 name : `str` (optional)
55 detType : `lsst.afw.cameraGeom.DetectorType` (optional)
57 serial : `str` (optional)
59 bbox : `lsst.geom.Box2I` (optional)
60 Bounding box; defaults to (0, 0), (1024x1024).
61 numAmps : `int` (optional)
63 pixelSize : `lsst.geom.Point2D` (optional)
65 ampExtent : `lsst.geom.Extent2I` (optional)
66 Dimensions of amplifier image bbox.
67 orientation : `lsst.afw.cameraGeom.Orientation` (optional)
68 Orientation of CCC in focal plane.
69 plateScale : `float` (optional)
70 Plate scale in arcsec/mm; 20.0 is for LSST.
71 radialDistortion : `float` (optional)
72 Radial distortion, in mm/rad^2.
73 The r^3 coefficient of the radial distortion polynomial
74 that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm;
75 0.925 is the value Dave Monet measured for lsstSim data
76 crosstalk : `iterable` (optional)
77 Crosstalk coefficient matrix. If None, then no crosstalk correction
79 modFunc : `callable` (optional)
80 A function that can modify attributes just before constructing the
81 detector; modFunc receives one argument: a DetectorWrapper with all
82 attributes except detector set.
83 physicalType : `str` (optional)
84 The physical type of the device, e.g. CCD, E2V, HgCdTe
90 detType=DetectorType.SCIENCE,
94 pixelSize=(0.02, 0.02),
96 orientation=Orientation(),
98 radialDistortion=0.925,
121 radialDistortCoeffs = [0.0, 1.0/pScaleRad,
127 focalPlaneToField=focalPlaneToField,
130 tanPixelSys = CameraSys(TAN_PIXELS, self.
namename)
131 actualPixelSys = CameraSys(ACTUAL_PIXELS, self.
namename)
134 tanPixelSys: pixelToTanPixel,
137 if crosstalk
is None:
138 crosstalk = [[0.0
for _
in range(numAmps)]
for _
in range(numAmps)]
141 if cameraBuilder
is None:
144 for i
in range(numAmps):
146 ampName = f
"amp {i + 1}"
147 ampBuilder.setName(ampName)
149 ampBuilder.setGain(1.71234e3)
150 ampBuilder.setReadNoise(0.521237e2)
151 ampBuilder.setReadoutCorner(ReadoutCorner.LL)
155 detectorBuilder = cameraBuilder.add(self.
namename, self.
idid)
156 detectorBuilder.setType(self.
typetype)
157 detectorBuilder.setSerial(self.
serialserial)
158 detectorBuilder.setPhysicalType(self.
physicalTypephysicalType)
159 detectorBuilder.setBBox(self.
bboxbbox)
160 detectorBuilder.setOrientation(self.
orientationorientation)
161 detectorBuilder.setPixelSize(self.
pixelSizepixelSize)
162 detectorBuilder.setTransformFromPixelsTo(tanPixelSys, self.
transMaptransMap[tanPixelSys])
163 detectorBuilder.setTransformFromPixelsTo(actualPixelSys, self.
transMaptransMap[actualPixelSys])
164 detectorBuilder.setCrosstalk(np.array(self.
crosstalkcrosstalk, dtype=np.float32))
165 for ampBuilder
in self.
ampListampList:
166 detectorBuilder.append(ampBuilder)
167 camera = cameraBuilder.finish()
172 """A simple Camera and the data used to construct it
174 Intended for use with unit tests, thus saves some interesting information.
179 Plate scale in arcsec/mm; 20.0 is for LSST.
180 radialDistortion : `float`
181 Radial distortion, in mm/rad^2.
182 The r^3 coefficient of the radial distortion polynomial
183 that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm;
184 0.925 is the value Dave Monet measured for lsstSim data.
186 Make repository products with one raw image per amplifier (True)
187 or with one raw image per detector (False).
190 def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False):
192 self.
_afwTestDataDir_afwTestDataDir = os.path.join(afwDir,
"python",
"lsst",
"afw",
193 "cameraGeom",
"testData")
210 """Return the number of detectors"""
214 """Construct a list of DetectorConfig, one per detector
219 with open(detFile)
as fh:
220 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
222 els = line.rstrip().split(
"|")
223 detectorProps = dict([(name, el)
224 for name, el
in zip(names, els)])
225 detectors.append(detectorProps)
227 for i, detector
in enumerate(detectors):
228 detectorId = (i + 1) * 10
229 detectorName = detector[
'name']
231 detConfig.name = detectorName
232 detConfig.id = detectorId
233 detConfig.bbox_x0 = 0
234 detConfig.bbox_y0 = 0
235 detConfig.bbox_x1 = int(detector[
'npix_x']) - 1
236 detConfig.bbox_y1 = int(detector[
'npix_y']) - 1
237 detConfig.serial = str(detector[
'serial'])
238 detConfig.detectorType = int(detector[
'detectorType'])
239 detConfig.offset_x = float(detector[
'x'])
240 detConfig.offset_y = float(detector[
'y'])
241 detConfig.refpos_x = float(detector[
'refPixPos_x'])
242 detConfig.refpos_y = float(detector[
'refPixPos_y'])
243 detConfig.yawDeg = float(detector[
'yaw'])
244 detConfig.pitchDeg = float(detector[
'pitch'])
245 detConfig.rollDeg = float(detector[
'roll'])
246 detConfig.pixelSize_x = float(detector[
'pixelSize'])
247 detConfig.pixelSize_y = float(detector[
'pixelSize'])
248 detConfig.transposeDetector =
False
249 detConfig.transformDict.nativeSys = PIXELS.getSysName()
250 detectorConfigs.append(detConfig)
253 return detectorConfigs
256 """Construct a dict of list of Amplifer, one list per detector.
261 Path to amplifier data file.
263 If True then there is one raw image per amplifier;
264 if False then there is one raw image per detector.
267 'LL': ReadoutCorner.LL,
268 'LR': ReadoutCorner.LR,
269 'UR': ReadoutCorner.UR,
270 'UL': ReadoutCorner.UL,
273 with open(ampFile)
as fh:
274 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
276 els = line.rstrip().split(
"|")
277 ampProps = dict([(name, el)
for name, el
in zip(names, els)])
278 ampDataList.append(ampProps)
281 for ampData
in ampDataList:
282 if ampData[
'ccd_name']
in ampListDict:
283 ampList = ampListDict[ampData[
'ccd_name']]
284 self.
ampDataDictampDataDict[ampData[
'ccd_name']][
'namps'] += 1
287 ampListDict[ampData[
'ccd_name']] = ampList
288 self.
ampDataDictampDataDict[ampData[
'ccd_name']] = {
'namps': 1,
'linInfo': {}}
291 int(ampData[
'trimmed_ymin'])),
293 int(ampData[
'trimmed_ymax'])))
295 int(ampData[
'raw_ymin'])),
297 int(ampData[
'raw_ymax'])))
300 int(ampData[
'raw_data_ymin'])),
302 int(ampData[
'raw_data_ymax'])))
305 int(ampData[
'hoscan_ymin'])),
307 int(ampData[
'hoscan_ymax'])))
310 int(ampData[
'voscan_ymin'])),
312 int(ampData[
'voscan_ymax'])))
315 int(ampData[
'pscan_ymin'])),
317 int(ampData[
'pscan_ymax'])))
318 xoffset = int(ampData[
'x_offset'])
319 yoffset = int(ampData[
'y_offset'])
320 flipx = bool(int(ampData[
'flipx']))
321 flipy = bool(int(ampData[
'flipy']))
326 xExt = rawBbox.getDimensions().getX()
328 rawDataBbox.flipLR(xExt)
329 rawHOverscanBbox.flipLR(xExt)
330 rawVOverscanBbox.flipLR(xExt)
331 rawPrescanBbox.flipLR(xExt)
333 yExt = rawBbox.getDimensions().getY()
335 rawDataBbox.flipTB(yExt)
336 rawHOverscanBbox.flipTB(yExt)
337 rawVOverscanBbox.flipTB(yExt)
338 rawPrescanBbox.flipTB(yExt)
339 if not flipx
and not flipy:
341 elif flipx
and not flipy:
343 elif flipx
and flipy:
345 elif not flipx
and flipy:
348 raise RuntimeError(
"Couldn't find read corner")
352 rawBbox.shift(offext)
353 rawDataBbox.shift(offext)
354 rawHOverscanBbox.shift(offext)
355 rawVOverscanBbox.shift(offext)
356 rawPrescanBbox.shift(offext)
360 builder.setBBox(bbox)
361 builder.setRawXYOffset(offset)
362 builder.setName(str(ampData[
'name']))
363 builder.setReadoutCorner(readoutMap[readcorner])
364 builder.setGain(float(ampData[
'gain']))
365 builder.setReadNoise(float(ampData[
'readnoise']))
366 linCoeffs = np.array([float(ampData[
'lin_coeffs']), ], dtype=float)
367 builder.setLinearityCoeffs(linCoeffs)
368 builder.setLinearityType(str(ampData[
'lin_type']))
369 builder.setRawFlipX(flipx)
370 builder.setRawFlipY(flipy)
371 builder.setRawBBox(rawBbox)
372 builder.setRawDataBBox(rawDataBbox)
373 builder.setRawHorizontalOverscanBBox(rawHOverscanBbox)
374 builder.setRawVerticalOverscanBBox(rawVOverscanBbox)
375 builder.setRawPrescanBBox(rawPrescanBbox)
376 builder.setLinearityThreshold(float(ampData[
'lin_thresh']))
377 builder.setLinearityMaximum(float(ampData[
'lin_max']))
378 builder.setLinearityUnits(str(ampData[
'lin_units']))
379 self.
ampDataDictampDataDict[ampData[
'ccd_name']][
'linInfo'][ampData[
'name']] = \
380 {
'lincoeffs': linCoeffs,
'lintype': str(ampData[
'lin_type']),
381 'linthresh': float(ampData[
'lin_thresh']),
'linmax': float(ampData[
'lin_max']),
382 'linunits': str(ampData[
'lin_units'])}
383 ampList.append(builder)
387 """Make camera config and amp catalog dictionary, using default
388 detector and amp files.
393 If True then there is one raw image per amplifier;
394 if False then there is one raw image per detector.
396 detFile = os.path.join(self.
_afwTestDataDir_afwTestDataDir,
"testCameraDetectors.dat")
398 ampFile = os.path.join(self.
_afwTestDataDir_afwTestDataDir,
"testCameraAmps.dat")
399 ampListDict = self.
makeAmpListsmakeAmpLists(ampFile, isLsstLike=isLsstLike)
401 camConfig.name =
"testCamera%s"%(
'LSST' if isLsstLike
else 'SC')
402 camConfig.detectorList = dict((i, detConfig)
403 for i, detConfig
in enumerate(detectorConfigs))
404 camConfig.plateScale = self.
plateScaleplateScale
406 radialDistortCoeffs = [0.0, 1.0/pScaleRad,
409 tConfig.transform.name =
'inverted'
410 radialClass = afwGeom.transformRegistry[
'radial']
411 tConfig.transform.active.transform.retarget(radialClass)
412 tConfig.transform.active.transform.coeffs = radialDistortCoeffs
414 tmc.nativeSys = FOCAL_PLANE.getSysName()
415 tmc.transforms = {FIELD_ANGLE.getSysName(): tConfig}
416 camConfig.transformDict = tmc
417 return camConfig, ampListDict
422 """Compare two Point2D(Point2D) functions by evaluating them over a
427 dVal = (maxVal - minVal) / (nVal - 1)
428 for xInd
in range(nVal):
429 x = minVal + (xInd * dVal)
430 for yInd
in range(nVal):
431 y = minVal + (yInd * dVal)
433 res1 = func1(fromPoint)
434 res2 = func2(fromPoint)
435 self.assertPairsAlmostEqual(res1, res2)
440 """Compare two TransformMaps.
442 self.assertEqual(
list(map1),
list(map2))
445 with self.subTest(sysFrom=sysFrom, sysTo=sysTo):
446 transform1 = map1.getTransform(sysFrom, sysTo)
447 transform2 = map2.getTransform(sysFrom, sysTo)
448 self.compare2DFunctions(transform1.applyForward, transform2.applyForward, **kwds)
449 self.compare2DFunctions(transform1.applyInverse, transform2.applyInverse, **kwds)
454 self.assertEqual(amp1.getName(), amp2.getName())
455 self.assertEqual(amp1.getBBox(), amp2.getBBox())
456 self.assertEqual(amp1.getGain(), amp2.getGain())
457 self.assertEqual(amp1.getReadNoise(), amp2.getReadNoise())
458 self.assertEqual(amp1.getSaturation(), amp2.getSaturation())
459 self.assertEqual(amp1.getReadoutCorner(), amp2.getReadoutCorner())
460 self.assertEqual(amp1.getSuspectLevel(), amp2.getSuspectLevel())
461 self.assertEqual(amp1.getLinearityCoeffs().shape, amp2.getLinearityCoeffs().shape)
462 self.assertFloatsEqual(amp1.getLinearityCoeffs(), amp2.getLinearityCoeffs())
463 self.assertEqual(amp1.getLinearityType(), amp2.getLinearityType())
464 self.assertEqual(amp1.getLinearityThreshold(), amp2.getLinearityThreshold())
465 self.assertEqual(amp1.getLinearityMaximum(), amp2.getLinearityMaximum())
466 self.assertEqual(amp1.getLinearityUnits(), amp2.getLinearityUnits())
467 self.assertEqual(amp1.getRawBBox(), amp2.getRawBBox())
468 self.assertEqual(amp1.getRawDataBBox(), amp2.getRawDataBBox())
469 self.assertEqual(amp1.getRawFlipX(), amp2.getRawFlipX())
470 self.assertEqual(amp1.getRawFlipY(), amp2.getRawFlipY())
471 self.assertEqual(amp1.getRawHorizontalOverscanBBox(), amp2.getRawHorizontalOverscanBBox())
472 self.assertEqual(amp1.getRawVerticalOverscanBBox(), amp2.getRawVerticalOverscanBBox())
473 self.assertEqual(amp1.getRawPrescanBBox(), amp2.getRawPrescanBBox())
478 """Compare two Detectors.
480 self.assertEqual(detector1.getName(), detector2.getName())
481 self.assertEqual(detector1.getId(), detector2.getId())
482 self.assertEqual(detector1.getSerial(), detector2.getSerial())
483 self.assertEqual(detector1.getPhysicalType(), detector2.getPhysicalType())
484 self.assertEqual(detector1.getBBox(), detector2.getBBox())
485 self.assertEqual(detector1.getPixelSize(), detector2.getPixelSize())
486 orientationIn = detector1.getOrientation()
487 orientationOut = detector2.getOrientation()
488 self.assertEqual(orientationIn.getFpPosition(), orientationOut.getFpPosition())
489 self.assertEqual(orientationIn.getReferencePoint(), orientationOut.getReferencePoint())
490 self.assertEqual(orientationIn.getYaw(), orientationOut.getYaw())
491 self.assertEqual(orientationIn.getPitch(), orientationOut.getPitch())
492 self.assertEqual(orientationIn.getRoll(), orientationOut.getRoll())
493 self.assertFloatsEqual(detector1.getCrosstalk(), detector2.getCrosstalk())
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)
502 """Compare two DetectorCollections.
504 self.assertCountEqual(
list(collection1.getNameIter()),
list(collection2.getNameIter()))
505 for k
in collection1.getNameIter():
506 self.assertDetectorsEqual(collection1[k], collection2[k], **kwds)
511 """Compare two Camers.
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())
A mutable Amplifier subclass class that can be used to incrementally construct or modify Amplifiers.
A helper class for creating and modifying cameras.
def makeDetectorConfigs(self, detFile)
def makeTestRepositoryItems(self, isLsstLike=False)
def makeAmpLists(self, ampFile, isLsstLike=False)
def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False)
def __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)
An integer coordinate rectangle.
daf::base::PropertyList * list
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def makeCameraFromAmpLists(cameraConfig, ampListDict, pupilFactoryClass=PupilFactory)
def makePixelToTanPixel(bbox, orientation, focalPlaneToField, pixelSizeMm)
def assertAmplifiersEqual(self, amp1, amp2)
def compare2DFunctions(self, func1, func2, minVal=-10, maxVal=None, nVal=5)
def assertTransformMapsEqual(self, map1, map2, **kwds)
def assertDetectorsEqual(self, detector1, detector2, **kwds)
def assertCamerasEqual(self, camera1, camera2, **kwds)
def assertDetectorCollectionsEqual(self, collection1, collection2, **kwds)
std::shared_ptr< TransformPoint2ToPoint2 > makeRadialTransform(std::vector< double > const &coeffs)
A purely radial polynomial distortion.
constexpr double arcsecToRad(double x) noexcept
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package